import {
  Table,
  Column,
  AllowNull,
  Default,
  ForeignKey,
  BelongsTo,
  DataType,
  BeforeSave,
} from "sequelize-typescript";
import { GrouparooRecord } from "./GrouparooRecord";
import { Property } from "./Property";
import { RecordPropertyOps } from "../modules/ops/recordProperty";
import { StateMachine } from "../modules/stateMachine";
import { APIData } from "../modules/apiData";
import { CommonModel } from "../classes/commonModel";
import { PropertiesCache } from "../modules/caches/propertiesCache";

const STATES = ["draft", "pending", "ready"] as const;

const STATE_TRANSITIONS: StateMachine.StateTransition[] = [
  { from: "draft", to: "ready", checks: [] },
  { from: "draft", to: "pending", checks: [] },
  { from: "pending", to: "ready", checks: [] },
  { from: "ready", to: "pending", checks: [] },
];

export enum InvalidReasons {
  Duplicate = "Duplicate Value",
}

@Table({ tableName: "recordProperties", paranoid: false })
export class RecordProperty extends CommonModel<RecordProperty> {
  idPrefix() {
    return "rpr";
  }

  @AllowNull(false)
  @ForeignKey(() => GrouparooRecord)
  @Column
  recordId: string;

  @AllowNull(false)
  @ForeignKey(() => Property)
  @Column
  propertyId: string;

  @AllowNull(false)
  @Default("pending")
  @Column(DataType.ENUM(...STATES))
  state: typeof STATES[number];

  @Column
  rawValue: string;

  @Column
  invalidValue: string;

  @Column
  invalidReason: string;

  @AllowNull(false)
  @Default(0)
  @Column
  position: number;

  @AllowNull(false)
  @Default(false)
  @Column
  unique: boolean;

  @Column
  valueChangedAt: Date;

  @AllowNull(false)
  @Default(new Date())
  @Column
  stateChangedAt: Date;

  @Column
  confirmedAt: Date;

  @Column
  startedAt: Date;

  @BelongsTo(() => GrouparooRecord)
  record: GrouparooRecord;

  @BelongsTo(() => Property)
  property: Property;

  async apiData() {
    const property = await this.ensureProperty();

    return {
      recordId: this.recordId,
      property: this.property,
      state: this.state,
      valueChangedAt: APIData.formatDate(this.valueChangedAt),
      stateChangedAt: APIData.formatDate(this.stateChangedAt),
      confirmedAt: APIData.formatDate(this.confirmedAt),
      startedAt: APIData.formatDate(this.startedAt),
      position: this.position,
      unique: this.unique,
      key: property.key,
      value: await this.getValue(),
      invalidValue: this.invalidValue,
      invalidReason: this.invalidReason,
    };
  }

  async getValue() {
    const property = await this.ensureProperty();
    return RecordPropertyOps.getValue(this.rawValue, property.type);
  }

  async setValue(value: any) {
    const property = await this.ensureProperty();
    const { rawValue, invalidValue, invalidReason } =
      await RecordPropertyOps.buildRawValue(value, property.type, this);

    this.rawValue = rawValue;
    this.invalidValue = invalidValue;
    this.invalidReason = invalidReason;
  }

  async ensureProperty() {
    const property = await PropertiesCache.findOneWithCache(
      this.propertyId,
      undefined,
      "ready"
    );
    if (!property) {
      throw new Error(`property not found for propertyId ${this.propertyId}`);
    }
    return property;
  }

  // --- Class Methods --- //

  @BeforeSave
  static async updateState(instance: RecordProperty) {
    await StateMachine.transition(instance, STATE_TRANSITIONS);
  }
}
