import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { EntityManager } from '@mikro-orm/postgresql';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Payment } from '../../payments/entities/payment.entity';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
import { InventoryService } from '../../inventory/inventory.service';
import { OrderStatus } from '../interface/order.interface';
import { Order } from '../entities/order.entity';
import { OrderStatusChangedEvent } from '../events/order.events';

@Injectable()
export class OrdersCrone {
  private readonly logger = new Logger(OrdersCrone.name);

  constructor(
    private readonly em: EntityManager,
    private readonly inventoryService: InventoryService,
    private readonly eventEmitter: EventEmitter2,
  ) { }

  // run every minute and fail pending online payments older than 15 minutes
  @Cron('*/1 * * * *', {
    name: 'failOldOnlinePayments',
    timeZone: 'UTC',
  })
  async cancelOldOnlinePendingOrders() {
    try {
      const cutoff = new Date(Date.now() - 15 * 60 * 1000);

      this.logger.debug('Searching for pending online payments older than 15 minutes');

      const payments = await this.em.find(
        Payment,
        {
          method: PaymentMethodEnum.Online,
          status: PaymentStatusEnum.Pending,
          createdAt: { $lte: cutoff },
        },
        { populate: ['order', 'order.items', 'order.items.food'] },
      );

      if (!payments || payments.length === 0) {
        return;
      }

      this.logger.log(`Found ${payments.length} stale pending online payments`);

      for (const p of payments) {
        try {
          await this.em.transactional(async em => {
            // reload inside transaction to avoid concurrency issues
            const payment = await em.findOne(
              Payment,
              { id: p.id },
              { populate: ['order', 'order.items', 'order.items.food'] },
            );
            if (!payment) return;
            if (payment.status !== PaymentStatusEnum.Pending) return;

            payment.status = PaymentStatusEnum.Failed;
            payment.failedAt = new Date();

            if (payment.order) {
              payment.order.status = OrderStatus.CANCELED;

              // prepare restore payload
              const items = (payment.order as any).items || [];
              const restorePayload = {
                items: items.map((it: any) => ({ foodId: it.food.id, quantity: it.quantity })),
              };

              if (restorePayload.items.length > 0) {
                await this.inventoryService.restoreToInventory(em, restorePayload);
              }
            }

            em.persist(payment);
            if (payment.order) em.persist(payment.order);
            await em.flush();
            this.logger.log(
              `Marked payment ${payment.id} and order ${payment.order?.id} as failed and restored inventory`,
            );
          });
        } catch (err) {
          this.logger.error(`Error processing payment ${p.id}: ${err.message}`, err.stack);
        }
      }
    } catch (err) {
      this.logger.error(`OrdersCrone failed: ${err.message}`, err.stack);
    }
  }

  // run every 15 minutes to complete orders that have been in shipped/delivered statuses for more than 3 hours
  @Cron('*/15 * * * *', {
    name: 'completeOldDeliveredOrders',
    timeZone: 'UTC',
  })
  async completeOldDeliveredOrders() {
    try {
      const cutoff = new Date(Date.now() - 3 * 60 * 60 * 1000); // 3 hours ago

      this.logger.debug('Searching for orders in shipped/delivered statuses older than 3 hours');

      const orders = await this.em.find(
        Order,
        {
          status: {
            $in: [
              OrderStatus.SHIPPED,
              OrderStatus.DELIVERED_TO_WAITER,
              OrderStatus.DELIVERED_TO_RECEPTIONIST,
            ],
          },
          updatedAt: { $lte: cutoff },
        },
        { populate: ['restaurant'] },
      );

      if (!orders || orders.length === 0) {
        return;
      }

      this.logger.log(`Found ${orders.length} orders to mark as completed`);

      for (const order of orders) {
        try {
          await this.em.transactional(async em => {
            // reload inside transaction to avoid concurrency issues
            const reloadedOrder = await em.findOne(
              Order,
              { id: order.id },
              { populate: ['restaurant', 'user'] },
            );
            if (!reloadedOrder) return;
            if (
              ![
                OrderStatus.SHIPPED,
                OrderStatus.DELIVERED_TO_WAITER,
                OrderStatus.DELIVERED_TO_RECEPTIONIST,
              ].includes(reloadedOrder.status)
            ) {
              return;
            }

            const previousStatus = reloadedOrder.status;
            const restaurantId =
              typeof reloadedOrder.restaurant === 'string'
                ? reloadedOrder.restaurant
                : reloadedOrder.restaurant.id;

            // Update order status and history
            reloadedOrder.status = OrderStatus.COMPLETED;
            reloadedOrder.history.push({
              status: OrderStatus.COMPLETED,
              changedAt: new Date(),
              desc: 'تکمیل سفارش توسط سیستم',
            });

            em.persist(reloadedOrder);
            await em.flush();

            // // Emit event after transaction completes
            this.eventEmitter.emit(
              OrderStatusChangedEvent.name,
              new OrderStatusChangedEvent(
                reloadedOrder.id,
                reloadedOrder.user?.id || '',
                String(reloadedOrder.orderNumber) || '',
                restaurantId,
                previousStatus,
                OrderStatus.COMPLETED,
                'admin',
              ),
            );

            this.logger.log(`Marked order ${reloadedOrder.id} as completed`);
          });
        } catch (err) {
          this.logger.error(`Error processing order ${order.id}: ${err.message}`, err.stack);
        }
      }
    } catch (err) {
      this.logger.error(`completeOldDeliveredOrders cron failed: ${err.message}`, err.stack);
    }
  }
}
