import { Injectable, UnauthorizedException } from '@nestjs/common';
import { TicketDataService } from '../data/services/ticket.service';
import { QueueOrder } from './entities/order.entity';
import { Queue } from './entities/queue.entity';
import {
  QueueDataService,
  QueueOrderService,
  QueueService,
} from '../data/services/queue.service';
import { RegisterQueueManager } from './usecases/register-queue.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { RegisterQueueDto } from '../infrastructure/controllers/dto/register-queue.dto';
import { CustomerQueueSummaryManager } from './usecases/queue/customer-queue-summary.manager';
import { CustomerQueueDetailManager } from './usecases/queue/customer-queue-detail.manager';
import { CustomerQueueItemManager } from './usecases/queue/customer-queue-item.manager';
import { CustomerQueueItemListManager } from './usecases/queue/customer-queue-item-list.manager';
import { CustomerQueueListManager } from './usecases/queue/customer-queue-list.manager';
import { SplitQueueDto } from '../infrastructure/controllers/dto/split-queue.dto';
import { SplitQueueManager } from './usecases/split-queue.manager';
import { LoginQueueDto } from '../infrastructure/controllers/dto/login-queue.dto';
import * as moment from 'moment';
import { CustomerQueueTicketSummaryManager } from './usecases/queue/customer-queue-ticket.manager';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { CustomerQueuePosItemManager } from './usecases/queue/customer-queue-pos-item.manager';
import {
  QueueModel,
  QueueOrderModel,
  QueueTicketModel,
} from '../data/models/queue.model';
import { CustomerQueueRecommendManager } from './usecases/queue/customer-queue-recommend.manager';
import { GenerateQueueManager } from './usecases/generate-queue.manager';

@Injectable()
export class QueueOrchestrator {
  constructor(
    public readonly dataService: TicketDataService,
    public readonly transactionService: TransactionDataService,
    private readonly queueService: QueueService,
    private readonly queueOrderService: QueueOrderService,
    private readonly registerQueueManager: RegisterQueueManager,
    private readonly splitQueueManager: SplitQueueManager,
    private readonly queueDataService: QueueDataService,
    private readonly generateQueueManager: GenerateQueueManager,
  ) {}

  async generate(data: any): Promise<QueueModel[]> {
    return await this.generateQueueManager.generate(data);
  }

  async loginCustomer(id: string): Promise<QueueOrder> {
    try {
      const order = await this.dataService.loginQueue(id);
      const code = order.tickets[0].code ?? order.code;
      const tickets = await this.dataService.ticketByCode(code);
      order.tickets = tickets;
      return order;
    } catch (error) {
      throw new UnauthorizedException({
        message: 'Invoice tidak ditemukan untuk tanggal hari ini',
        error: 'INVOICE_NOT_FOUND',
      });
    }
  }

  async loginPhone(login: LoginQueueDto): Promise<QueueOrder> {
    const { name, phone } = login;
    const now = moment().format('DD/MM/YYYY');
    try {
      const order = await this.dataService.loginPhone(name, phone);
      const code = order.tickets[0].code ?? order.code;
      const tickets = await this.dataService.ticketByCode(code);
      order.tickets = tickets;
      return order;
    } catch (error) {
      throw new UnauthorizedException({
        message: `Antrian atas nama ${name} dan nomor telepon ${phone} pada tanggal ${now} tidak ditemukan`,
        error: 'INVOICE_NOT_FOUND',
      });
    }
  }

  async create(data: RegisterQueueDto): Promise<Queue> {
    const queue = await this.queueService.getTicketItems(
      data.ticket_id,
      data.item_id,
    );
    const queueRequest: any = {
      qty: data.qty,
      item_id: queue.id,
    };
    this.registerQueueManager.setData(queueRequest);
    this.registerQueueManager.setService(this.queueService, TABLE_NAME.QUEUE);
    await this.registerQueueManager.execute();
    return this.registerQueueManager.getResult();
  }

  async split(data: SplitQueueDto): Promise<QueueOrder> {
    const queueIds = data.items.map((i) => i.queue_item_id);
    const queue = await this.dataService.orderItems(data.order_id, queueIds);

    const existTicket = await this.dataService.ticketByUser(
      data.name,
      data.phone,
    );

    if (existTicket) {
      const itemTickets = this.itemsFromOrder(queue);

      const items = data.items.map((item) => {
        const item_id = itemTickets[item.queue_item_id];
        return {
          item_queue_id: item_id,
          item_id: item_id,
          qty: item.qty,
        };
      });
      existTicket.items.push(...items);
      await this.dataService.updateQueueTicket(existTicket);

      data.items.forEach((item) => {
        this.queueService.updateItemQty(item.queue_item_id, item.qty);
      });

      return queue;
    }

    this.splitQueueManager.setRequestData(data);
    this.splitQueueManager.setData(queue);
    this.splitQueueManager.setService(
      this.queueOrderService,
      TABLE_NAME.QUEUE_ORDER,
    );
    await this.splitQueueManager.execute();
    return this.splitQueueManager.getResult();
  }

  itemsFromOrder(order: QueueOrderModel) {
    const itemTickets = {};
    order.tickets.forEach((ticket) => {
      ticket.items.forEach((item) => {
        itemTickets[item.id] = item.item_id;
      });
    });

    return itemTickets;
  }

  async queuePOSTickets(order_id: string): Promise<any> {
    const tickets = await this.dataService.queuePosTickets(order_id);
    const manager = new CustomerQueuePosItemManager(tickets);
    return manager.data;
  }

  async queueTickets(order_id: string): Promise<any> {
    const tickets = await this.dataService.queueTickets(order_id);
    const manager = new CustomerQueueSummaryManager(tickets);
    return manager.data;
  }

  async queueTicketDetail(order_id: string, ticket_id: string): Promise<any> {
    const tickets = await this.dataService.queueTicketItems(
      order_id,
      ticket_id,
    );

    const queueItemIds = this.getQueueItemFromTickets(tickets);
    const queues = await this.queueService.queues(queueItemIds);
    const manager = new CustomerQueueDetailManager(tickets);
    manager.currentQueues(queues);
    return manager.data;
  }

  async queueTicketSummary(order_id: string, ticket_id: string): Promise<any> {
    const tickets = await this.dataService.queueTicketActive(
      order_id,
      ticket_id,
    );

    const manager = new CustomerQueueTicketSummaryManager(tickets);
    return manager.data;
  }

  async queueItemDetail(order_id: string, item_id: string): Promise<any> {
    const tickets = await this.dataService.queueItemTickets(order_id, item_id);
    const manager = new CustomerQueueItemManager(tickets);
    return manager.data;
  }

  async queueItemRecommendation(order_id: string): Promise<any> {
    const tickets = await this.dataService.queueTickets(order_id);
    const queueItemIds = this.getQueueItemFromTickets(tickets);
    const recommendItems = await this.queueDataService.exclude(queueItemIds);
    const recommendIds = recommendItems.map((item) => item.id);
    const queues = await this.queueService.queues(recommendIds);
    const manager = new CustomerQueueRecommendManager(tickets);
    manager.currentQueues(queues);
    return manager.recommend(recommendItems);
  }

  async queueItemSummary(): Promise<any> {
    const recommendItems = await this.queueDataService.allQueue();
    const recommendIds = recommendItems.map((item) => item.id);
    const queues = await this.queueService.queues(recommendIds);
    const manager = new CustomerQueueRecommendManager(null);
    manager.currentQueues(queues);
    return manager.recommend(recommendItems);
  }

  async queueItems(order_id: string): Promise<any> {
    const tickets = await this.dataService.queueTickets(order_id);
    const queueItemIds = this.getQueueItemFromTickets(tickets);
    const queues = await this.queueService.queues(queueItemIds);
    const manager = new CustomerQueueItemListManager(tickets);
    manager.currentQueues(queues);
    return manager.data;
  }

  async queueUniqueItems(order_id: string): Promise<any> {
    const tickets = await this.dataService.queueUniqueTickets(order_id);
    const queueItemIds = this.getQueueItemFromTickets(tickets);
    const queues = await this.queueService.queues(queueItemIds);
    const manager = new CustomerQueueItemListManager(tickets);
    manager.currentQueues(queues);
    return manager.data;
  }

  async queueOrderItems(order_id: string): Promise<any> {
    const tickets = await this.dataService.queueTickets(order_id);
    const manager = new CustomerQueueListManager(tickets);
    return manager.data;
  }

  getQueueItemFromTickets(tickets: QueueTicketModel[]) {
    const queueItemIds = tickets.map((ticket) => {
      return ticket.items.map((item) => {
        return item.item.item_queue_id;
      });
    });

    return queueItemIds.flat();
  }
}
