/**
 * ComparativoService - Servicio de comparativos de cotizaciones
 *
 * Gestión de cuadros comparativos para selección de proveedores.
 *
 * @module Purchase
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { ComparativoCotizaciones, ComparativoStatus } from '../entities/comparativo-cotizaciones.entity';
import { ComparativoProveedor } from '../entities/comparativo-proveedor.entity';
import { ComparativoProducto } from '../entities/comparativo-producto.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateComparativoDto {
  requisicionId?: string;
  name: string;
  comparisonDate: Date;
  notes?: string;
}

export interface AddProveedorDto {
  supplierId: string;
  quotationNumber?: string;
  quotationDate?: Date;
  deliveryDays?: number;
  paymentConditions?: string;
  evaluationNotes?: string;
}

export interface AddProductoDto {
  productId: string;
  quantity: number;
  unitPrice: number;
  notes?: string;
}

export interface ComparativoFilters {
  requisicionId?: string;
  status?: ComparativoStatus;
  dateFrom?: Date;
  dateTo?: Date;
}

export class ComparativoService {
  constructor(
    private readonly comparativoRepository: Repository<ComparativoCotizaciones>,
    private readonly proveedorRepository: Repository<ComparativoProveedor>,
    private readonly productoRepository: Repository<ComparativoProducto>
  ) {}

  private generateCode(): string {
    const now = new Date();
    const year = now.getFullYear().toString().slice(-2);
    const month = (now.getMonth() + 1).toString().padStart(2, '0');
    const random = Math.random().toString(36).substring(2, 6).toUpperCase();
    return `CMP-${year}${month}-${random}`;
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: ComparativoFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ComparativoCotizaciones>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.comparativoRepository
      .createQueryBuilder('comp')
      .leftJoinAndSelect('comp.requisicion', 'requisicion')
      .leftJoinAndSelect('comp.createdBy', 'createdBy')
      .where('comp.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('comp.deleted_at IS NULL');

    if (filters.requisicionId) {
      queryBuilder.andWhere('comp.requisicion_id = :requisicionId', {
        requisicionId: filters.requisicionId,
      });
    }

    if (filters.status) {
      queryBuilder.andWhere('comp.status = :status', { status: filters.status });
    }

    if (filters.dateFrom) {
      queryBuilder.andWhere('comp.comparison_date >= :dateFrom', { dateFrom: filters.dateFrom });
    }

    if (filters.dateTo) {
      queryBuilder.andWhere('comp.comparison_date <= :dateTo', { dateTo: filters.dateTo });
    }

    queryBuilder
      .orderBy('comp.comparison_date', 'DESC')
      .skip(skip)
      .take(limit);

    const [data, total] = await queryBuilder.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async findById(ctx: ServiceContext, id: string): Promise<ComparativoCotizaciones | null> {
    return this.comparativoRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<ComparativoCotizaciones>,
    });
  }

  async findWithDetails(ctx: ServiceContext, id: string): Promise<ComparativoCotizaciones | null> {
    return this.comparativoRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: null,
      } as unknown as FindOptionsWhere<ComparativoCotizaciones>,
      relations: [
        'requisicion',
        'createdBy',
        'approvedBy',
        'proveedores',
        'proveedores.productos',
      ],
    });
  }

  async create(ctx: ServiceContext, dto: CreateComparativoDto): Promise<ComparativoCotizaciones> {
    const comparativo = this.comparativoRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      code: this.generateCode(),
      requisicionId: dto.requisicionId,
      name: dto.name,
      comparisonDate: dto.comparisonDate,
      notes: dto.notes,
      status: 'draft',
    });

    return this.comparativoRepository.save(comparativo);
  }

  async addProveedor(
    ctx: ServiceContext,
    comparativoId: string,
    dto: AddProveedorDto
  ): Promise<ComparativoProveedor> {
    const comparativo = await this.findById(ctx, comparativoId);
    if (!comparativo) {
      throw new Error('Comparativo not found');
    }

    if (comparativo.status !== 'draft' && comparativo.status !== 'in_evaluation') {
      throw new Error('Cannot add suppliers to approved or cancelled comparisons');
    }

    const proveedor = this.proveedorRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      comparativoId,
      supplierId: dto.supplierId,
      quotationNumber: dto.quotationNumber,
      quotationDate: dto.quotationDate,
      deliveryDays: dto.deliveryDays,
      paymentConditions: dto.paymentConditions,
      evaluationNotes: dto.evaluationNotes,
    });

    return this.proveedorRepository.save(proveedor);
  }

  async addProducto(
    ctx: ServiceContext,
    proveedorId: string,
    dto: AddProductoDto
  ): Promise<ComparativoProducto> {
    const proveedor = await this.proveedorRepository.findOne({
      where: { id: proveedorId, tenantId: ctx.tenantId } as FindOptionsWhere<ComparativoProveedor>,
      relations: ['comparativo'],
    });

    if (!proveedor) {
      throw new Error('Supplier entry not found');
    }

    if (proveedor.comparativo.status !== 'draft' && proveedor.comparativo.status !== 'in_evaluation') {
      throw new Error('Cannot add products to approved or cancelled comparisons');
    }

    const producto = this.productoRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      comparativoProveedorId: proveedorId,
      productId: dto.productId,
      quantity: dto.quantity,
      unitPrice: dto.unitPrice,
      notes: dto.notes,
    });

    return this.productoRepository.save(producto);
  }

  async startEvaluation(ctx: ServiceContext, id: string): Promise<ComparativoCotizaciones | null> {
    const comparativo = await this.findWithDetails(ctx, id);
    if (!comparativo) {
      return null;
    }

    if (comparativo.status !== 'draft') {
      throw new Error('Can only start evaluation on draft comparisons');
    }

    if (!comparativo.proveedores || comparativo.proveedores.length < 2) {
      throw new Error('Need at least 2 suppliers to start evaluation');
    }

    comparativo.status = 'in_evaluation';
    return this.comparativoRepository.save(comparativo);
  }

  async selectWinner(ctx: ServiceContext, id: string, supplierId: string): Promise<ComparativoCotizaciones | null> {
    const comparativo = await this.findWithDetails(ctx, id);
    if (!comparativo) {
      return null;
    }

    if (comparativo.status !== 'in_evaluation') {
      throw new Error('Can only select winner during evaluation');
    }

    // Verify supplier is in the comparison
    const proveedor = comparativo.proveedores?.find((p) => p.supplierId === supplierId);
    if (!proveedor) {
      throw new Error('Supplier not found in this comparison');
    }

    // Mark all as not selected, then mark winner
    for (const p of comparativo.proveedores || []) {
      p.isSelected = p.supplierId === supplierId;
      await this.proveedorRepository.save(p);
    }

    comparativo.winnerSupplierId = supplierId;
    comparativo.status = 'approved';
    comparativo.approvedById = ctx.userId || '';
    comparativo.approvedAt = new Date();

    return this.comparativoRepository.save(comparativo);
  }

  async cancel(ctx: ServiceContext, id: string): Promise<ComparativoCotizaciones | null> {
    const comparativo = await this.findById(ctx, id);
    if (!comparativo) {
      return null;
    }

    if (comparativo.status === 'approved') {
      throw new Error('Cannot cancel approved comparisons');
    }

    comparativo.status = 'cancelled';
    return this.comparativoRepository.save(comparativo);
  }

  async updateProveedorTotal(ctx: ServiceContext, proveedorId: string): Promise<ComparativoProveedor | null> {
    const proveedor = await this.proveedorRepository.findOne({
      where: { id: proveedorId, tenantId: ctx.tenantId } as FindOptionsWhere<ComparativoProveedor>,
      relations: ['productos'],
    });

    if (!proveedor) {
      return null;
    }

    const total = proveedor.productos?.reduce(
      (sum, p) => sum + (p.quantity * p.unitPrice),
      0
    ) || 0;

    proveedor.totalAmount = total;
    return this.proveedorRepository.save(proveedor);
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const comparativo = await this.findById(ctx, id);
    if (!comparativo) {
      return false;
    }

    if (comparativo.status === 'approved') {
      throw new Error('Cannot delete approved comparisons');
    }

    await this.comparativoRepository.update(
      { id, tenantId: ctx.tenantId } as unknown as FindOptionsWhere<ComparativoCotizaciones>,
      { deletedAt: new Date(), deletedById: ctx.userId }
    );

    return true;
  }
}
