import { Injectable } from '@nestjs/common';

import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { Between, DataSource, In, Not, Repository } from 'typeorm';
import {
  QueueItemModel,
  QueueModel,
  QueueOrderModel,
} from '../models/queue.model';
import { BaseReadService } from 'src/core/modules/data/service/base-read.service';
import { BaseDataService } from 'src/core/modules/data/service/base-data.service';
import { ItemQueueModel } from 'src/modules/item-related/item-queue/data/models/item-queue.model';
import * as moment from 'moment';
import * as math from 'mathjs';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';

@Injectable()
export class QueueDataService extends BaseReadService<QueueModel> {
  constructor(
    @InjectRepository(QueueModel, CONNECTION_NAME.DEFAULT)
    private repo: Repository<QueueModel>,

    @InjectRepository(ItemQueueModel, CONNECTION_NAME.DEFAULT)
    private itemQueueRepo: Repository<ItemQueueModel>,
  ) {
    super(repo);
  }

  async waitingQueue(item_queue_id: string) {
    const start = moment().startOf('day').valueOf();
    const end = moment().endOf('day').valueOf();
    return this.repo.find({
      where: {
        time: Between(start, end),
        item_queue_id,
        status: 'waiting',
      },
      order: {
        time: 'ASC',
      },
    });
  }

  async doneQueue(item_queue_id: string) {
    const start = moment().startOf('day').valueOf();
    const end = moment().endOf('day').valueOf();
    return this.repo.find({
      where: {
        time: Between(start, end),
        item_queue_id,
        status: In(['done', 'called']),
      },
      order: {
        time: 'ASC',
      },
    });
  }

  async exclude(item_queue_id: string[]) {
    const queues = await this.itemQueueRepo.find({
      relations: ['items'],
      where: {
        id: Not(In(item_queue_id)),
      },
    });

    return queues.filter((q) => q.items.length > 0);
  }

  async allQueue() {
    const queues = await this.itemQueueRepo.find({
      relations: ['items'],
    });

    return queues.filter((q) => q.items.length > 0);
  }

  async lastQueue(item_queue_id: string) {
    const start = moment().startOf('day').valueOf();
    const end = moment().endOf('day').valueOf();
    return this.repo.findOne({
      where: {
        time: Between(start, end),
        item_queue_id,
        status: 'called',
      },
      order: {
        call_time: 'DESC',
      },
    });
  }

  /**
   * @deprecated
   * Change to QueueTimeFormula (queue-time.formula.ts)
   * @param item_queue_id
   * @returns
   */
  async queueTimes(item_queue_id: string): Promise<any> {
    const queueTimes = {};
    let now = moment().valueOf();
    const itemQueue = await this.itemQueueRepo.findOne({
      relations: ['items'],
      where: {
        id: item_queue_id,
      },
    });

    const times = itemQueue.items.map((item) => item.play_estimation ?? 0);
    const average = math.mean(times) * 60 * 1000; // change average minute to milliseconds
    const queues = await this.repo.find({
      where: {
        item_queue_id,
        status: 'waiting',
      },
    });

    queueTimes[queues[0].id] = now; // first queue will be now

    for (let i = 1; i < queues.length; i++) {
      const queue = queues[i];
      // duration will be total qty multiple by average
      const duration = queue.qty * average;

      // time to call will be now + duration
      const time = now + duration;
      queueTimes[queue.id] = time;

      // update now to last call time
      now = time;
    }

    return queueTimes;
  }

  async queueItems(item_queue_id: string[]): Promise<QueueModel[]> {
    return this.repo.find({
      relations: ['item', 'item.item', 'item.item.item_queue'],
      where: {
        item: { item: { item_queue: { id: In(item_queue_id) } } },
      },
      order: {
        time: 'DESC',
      },
    });
  }
}

@Injectable()
export class QueueService extends BaseDataService<QueueModel> {
  constructor(
    @InjectRepository(QueueModel, CONNECTION_NAME.DEFAULT)
    private repo: Repository<QueueModel>,

    @InjectRepository(QueueItemModel, CONNECTION_NAME.DEFAULT)
    private item: Repository<QueueItemModel>,

    @InjectRepository(ItemModel, CONNECTION_NAME.DEFAULT)
    private itemMaster: Repository<ItemModel>,

    @InjectDataSource(CONNECTION_NAME.DEFAULT)
    private dataSource: DataSource,
  ) {
    super(repo);
  }

  async queueTicket(queueId: string) {
    return this.repo.findOne({
      relations: ['item', 'item.ticket'],
      where: {
        id: queueId,
      },
    });
  }

  async queues(ids: string[]) {
    const start = moment().startOf('day').valueOf();
    const end = moment().endOf('day').valueOf();
    const playEstimations = {};
    const queuePeakLevel = {};

    for (const id of ids) {
      const est = await this.itemAverageTimeEstimation(id);
      playEstimations[id] = est.average;
      queuePeakLevel[id] = est.peakLevel;
    }

    const queues = await this.repo.find({
      where: {
        item_queue_id: In(ids),
        time: Between(start, end),
      },
      order: {
        time: 'ASC',
      },
    });

    queues.forEach((queue) => {
      queue.average = playEstimations[queue.item_queue_id];
      queue.peak_level = queuePeakLevel[queue.item_queue_id];
    });

    return queues;
  }

  async itemAverageTimeEstimation(item_queue_id: string) {
    const items = await this.itemMaster.find({
      relations: ['item_queue'],
      where: {
        item_queue_id,
      },
    });

    const times = items.map((item) => item.play_estimation ?? 0);
    const average = times.length > 0 ? math.mean(times) * 60 * 1000 : 0; // change average minute to milliseconds
    const peakLevel = items[0]?.item_queue?.max_peak_level ?? 100;
    return { average, peakLevel };
  }

  async getTicketItems(ticket_id: string, item_id: string) {
    return this.item.findOneOrFail({
      relations: ['item'],
      where: [
        {
          ticket_id,
          item_id,
        },
        {
          ticket_id,
          item: { item_queue_id: item_id },
        },
      ],
    });
  }

  async updateLastNotification(queue_id: string, time: number) {
    const query = `UPDATE queues SET last_notification = ${time} WHERE id = '${queue_id}'`;
    this.dataSource.query(query);
  }

  async updateItemQty(item_id: string, qty: number): Promise<void> {
    const query = `UPDATE queue_items SET qty = qty - ${qty} WHERE id = '${item_id}'`;
    this.dataSource.query(query);
  }
}

@Injectable()
export class QueueOrderService extends BaseDataService<QueueOrderModel> {
  constructor(
    @InjectRepository(QueueOrderModel, CONNECTION_NAME.DEFAULT)
    private repo: Repository<QueueOrderModel>,
  ) {
    super(repo);
  }
}
