import { Injectable } from '@nestjs/common';
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
import { FilterQuery } from '@mikro-orm/core';
import { Order } from '../entities/order.entity';
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
import { OrderStatus } from '../interface/order.interface';
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
import { Review } from '../../review/entities/review.entity';

type FindOrdersOpts = {
  page?: number;
  limit?: number;
  statuses?: OrderStatus[];
  paymentStatus?: PaymentStatusEnum;
  search?: string;
  startDate?: string;
  endDate?: string;
  orderBy?: string;
  order?: 'asc' | 'desc';
  userId?: string;
  excludeOnlinePendingPayment?: boolean;
};

@Injectable()
export class OrderRepository extends EntityRepository<Order> {
  constructor(readonly em: EntityManager) {
    super(em, Order);
  }

  /**
   * Find orders with pagination and optional filters.
   * Supports: statuses, paymentStatus, search (orderNumber), date range, ordering.
   */
  async findAllPaginated(restId: string, opts: FindOrdersOpts = {}): Promise<PaginatedResult<Order>> {
    const {
      page = 1,
      limit = 10,
      statuses,
      search,
      startDate,
      endDate,
      orderBy = 'createdAt',
      order = 'desc',
      userId,
      excludeOnlinePendingPayment = false,
    } = opts;

    const offset = (page - 1) * limit;

    const where: FilterQuery<Order> = { restaurant: { id: restId } };

    // Filter by statuses
    if (statuses) {
      // Ensure statuses is always an array and normalize it
      const normalizedStatuses = Array.isArray(statuses) ? statuses : [statuses];
      // Filter out any empty values
      const validStatuses = normalizedStatuses.filter((s): s is OrderStatus => !!s);

      if (validStatuses.length > 0) {
        // Use direct equality for single value, $in for multiple values to avoid SQL generation issues
        if (validStatuses.length === 1) {
          where.status = validStatuses[0];
        } else {
          where.status = { $in: validStatuses };
        }
      }
    }

    if (userId) {
      where.user = { id: userId };
    }

    // Filter by date range
    if (startDate || endDate) {
      where.createdAt = {};
      if (startDate) {
        where.createdAt.$gte = new Date(startDate);
      }
      if (endDate) {
        where.createdAt.$lte = new Date(endDate);
      }
    }

    // Search by order number or user information
    if (search) {
      const searchPattern = `%${search}%`;
      const searchConditions: any[] = [
        { user: { firstName: { $ilike: searchPattern } } },
        { user: { lastName: { $ilike: searchPattern } } },
        { user: { phone: { $ilike: searchPattern } } },
      ];

      // If search is numeric, also search by orderNumber
      const numericSearch = Number(search);
      if (!isNaN(numericSearch) && isFinite(numericSearch)) {
        searchConditions.push({ orderNumber: numericSearch });
      }

      where.$or = searchConditions;
    }

    // Filter: Exclude orders with payment method Online and status pending_payment
    if (excludeOnlinePendingPayment) {
      const existingConditions = where.$and || [];
      where.$and = [
        ...existingConditions,
        {
          $or: [
            { paymentMethod: { method: { $ne: PaymentMethodEnum.Online } } },
            { status: { $ne: OrderStatus.PENDING_PAYMENT } },
          ],
        },
      ];
    }

    // First, fetch orders without reviews
    const [data, total] = await this.findAndCount(where, {
      limit,
      offset,
      orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
      populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'items', 'items.food'] as never,
    });

    // Collect all (orderId, foodId) pairs for efficient review lookup
    const orderFoodPairs: Array<{ orderId: string; foodId: string }> = [];
    for (const order of data) {
      for (const item of order.items.getItems()) {
        if (item.food?.id) {
          orderFoodPairs.push({ orderId: order.id, foodId: item.food.id });
        }
      }
    }

    // Fetch all relevant reviews in a single query
    const reviewsMap: Map<string, string> = new Map();
    if (orderFoodPairs.length > 0) {
      const orderIds = [...new Set(orderFoodPairs.map(p => p.orderId))];
      const foodIds = [...new Set(orderFoodPairs.map(p => p.foodId))];

      const reviews = await this.em.find(
        Review,
        {
          order: { id: { $in: orderIds } },
          food: { id: { $in: foodIds } },
        },
        {
          fields: ['id', 'order', 'food'],
          populate: ['order', 'food'],
        },
      );

      // Create a map: key = `${orderId}-${foodId}`, value = reviewId
      for (const review of reviews) {
        const key = `${review.order.id}-${review.food.id}`;
        reviewsMap.set(key, review.id);
      }
    }

    // Map reviewIds to foods
    for (const order of data) {
      for (const item of order.items.getItems()) {
        if (item.food) {
          const key = `${order.id}-${item.food.id}`;
          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
          const food = item.food as any;
          // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
          food.reviewId = reviewsMap.get(key) || null;
        }
      }
    }

    const totalPages = Math.ceil(total / limit);

    return {
      data,
      meta: {
        total,
        page,
        limit,
        totalPages,
      },
    };
  }
}
