import MySQLModule from "./MySQLModule";
import { Command } from "./Command";
import { ReflectionMutableCommandProccessingAggregate } from "./ReflectionMutableCommandProccessingAggregate";
import { DomenEvent } from "./DomenEvent";
/**
 * Мы будем создавать агрегат репозитория с шаблонным кодом, передавая в нём название
 * сущности, за хранение которой он ответственнен.
 *  пример кода
 *
 * new TemoeratureRepository() {
 *    constructor() {
 *        super(Temperature)
 *    }
 * }
 *
 * const temoeratureRepository = new TemoeratureRepository();
 * temoeratureRepository.find(12)
 *
 */
export class AggregateRepository<T> {
  protected entity_type = "none";
  protected entity: Function;
  private rand: number;

  constructor(entity: T & ReflectionMutableCommandProccessingAggregate) {
    this.entity_type = entity.constructor.name;
    this.entity = entity.constructor;
    this.rand = Math.round(Math.random() * 100);
  }

  /**
   * Вставляет события в таблицу events, и создаёт сущность entities.
   */
  public async save(agregatCommand: Command): Promise<T> {
    let id = agregatCommand.entity_id;
    if (!id) {
      id = this.rand + "-" + Date.now();
    }
    // Проверим, исполнялась ли эта команда ранее
    let col = await MySQLModule.db.query(
      "SELECT * FROM `entities` WHERE `entity_type` = '" +
        this.entity_type +
        "' AND `entity_id` = '" +
        id +
        "'"
    );
    console.log(col.length);
    if (col.length > 0) throw new Error("Команда уже исполнялась");
    col = await MySQLModule.db.query(
      "INSERT INTO `entities`(`entity_type`, `entity_id`, `entity_version`) VALUES " +
        `('${this.entity_type}','${id}','1')`
    );
    console.log(col);
    agregatCommand.entity_id = id;
    const agregate = new (this.entity as any)();

    console.log(agregatCommand);

    let events = agregate.process(agregatCommand);
    events.forEach(async event => {
      agregate.apply(event);
      await this.saveEvent(event);
    });
    return agregate;
  }
  protected async saveEvent(event: DomenEvent) {
    let i = await MySQLModule.db.query(
      "INSERT INTO `events`(" +
        "`event_id`, `event_type`, `event_data`, `entity_type`, `entity_id`, `triggering_event`) VALUES " +
        `(NULL,'${event.event_type}','${JSON.stringify(event.event_data)}','${
          this.entity_type
        }','${event.entity_id}',${
          event.triggering_event ? "'" + event.triggering_event + "'" : "NULL"
        })`
    );
    // await this.broker.emit( event.entity.constructor.name.toLowerCase() + '.'+event.type.toLowerCase(),  JSON.stringify(event))
  }

  async find(
    id: string
  ): Promise<T & ReflectionMutableCommandProccessingAggregate> {
    //  поиск снимка
    const entity = this.entity_type;
    const snapshot: any[] = await MySQLModule.db.query(
      `SELECT * FROM \`snapshots\` WHERE \`entity_type\` = '${entity}' and \`entity_id\` = '${id}' LIMIT 0,1`
    );
    let agregate: T & ReflectionMutableCommandProccessingAggregate = null;
    let event = [];
    // if есть снимок
    if (snapshot[0]) {
      agregate = new (this.entity as any)(
        JSON.parse(snapshot[0].snapshot_json)
      );
      // ищем все события event_id > entity_version
      event = await MySQLModule.db.query(
        `SELECT * FROM \`events\` WHERE \`entity_type\` = '${entity}' and \`entity_id\` = '${id}' and \`event_id\` > '${snapshot[0].entity_version}' ORDER BY \`event_id\` ASC `
      );
    } else {
      agregate = new (this.entity as any)({});
      // берём все события этой сущности
      event = await MySQLModule.db.query(
        `SELECT * FROM \`events\` WHERE \`entity_type\` = '${entity}' and \`entity_id\` = '${id}'  ORDER BY \`create_at\` ASC`
      );
      console.log(
        `SELECT * FROM \`events\` WHERE \`entity_type\` = '${entity}' and \`entity_id\` = '${id}'  ORDER BY \`create_at\` ASC`
      );
    }
    console.log(event);
    event.forEach(r => {
      agregate.apply(new DomenEvent(r));
    });
    return agregate;
    // Получает текущую версию из entities
  }

  /**
   * Вставляет события в таблицу events, Обновляет версию до актуальной.
   */
  public async update(agregatCommand: Command) {
    const agregate = await this.find(agregatCommand.entity_id);
    let events = agregate.process(agregatCommand);
    console.log(agregatCommand, agregate, events);
    events.forEach(async event => {
      agregate.apply(event);
      await this.saveEvent(event);
    });
    agregatCommand.entity = agregate;
    return agregate;
  }
}
