/**
 * ProposalService - Gestion de Propuestas de Licitacion
 *
 * CRUD y logica de negocio para propuestas/ofertas de proveedores.
 * Gestiona evaluacion tecnica, economica y comparacion de propuestas.
 *
 * @module Bidding (MAI-018)
 */

import { Repository } from 'typeorm';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { Proposal, ProposalStatus } from '../entities/proposal.entity';
import { Tender } from '../entities/tender.entity';

// DTOs
export interface CreateProposalDto {
  tenderId: string;
  vendorId: string;
  proposedAmount: number;
  proposedScheduleDays: number;
  technicalProposalUrl?: string;
  economicProposalUrl?: string;
  submittedAt?: Date;
}

export interface UpdateProposalDto extends Partial<Omit<CreateProposalDto, 'tenderId' | 'vendorId'>> {
  status?: ProposalStatus;
}

export interface EvaluateProposalDto {
  technicalScore: number;
  economicScore: number;
}

export interface ProposalFilters {
  tenderId?: string;
  vendorId?: string;
  status?: ProposalStatus | ProposalStatus[];
  search?: string;
  page?: number;
  limit?: number;
}

export interface RankedProposal extends Proposal {
  id: string;
  rank: number;
  totalScore: number;
}

export class ProposalService {
  constructor(
    private readonly repository: Repository<Proposal>,
    private readonly tenderRepository: Repository<Tender>
  ) {}

  /**
   * Buscar propuestas con filtros y paginacion
   */
  async findAll(
    ctx: ServiceContext,
    filters: ProposalFilters = {}
  ): Promise<PaginatedResult<Proposal>> {
    const page = filters.page || 1;
    const limit = filters.limit || 20;

    const qb = this.repository
      .createQueryBuilder('p')
      .leftJoinAndSelect('p.tender', 't')
      .leftJoinAndSelect('p.vendor', 'v')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.deleted_at IS NULL');

    if (filters.tenderId) {
      qb.andWhere('p.tender_id = :tenderId', { tenderId: filters.tenderId });
    }
    if (filters.vendorId) {
      qb.andWhere('p.vendor_id = :vendorId', { vendorId: filters.vendorId });
    }
    if (filters.status) {
      if (Array.isArray(filters.status)) {
        qb.andWhere('p.status IN (:...statuses)', { statuses: filters.status });
      } else {
        qb.andWhere('p.status = :status', { status: filters.status });
      }
    }
    if (filters.search) {
      qb.andWhere(
        '(v.business_name ILIKE :search OR v.rfc ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    const skip = (page - 1) * limit;
    qb.orderBy('p.created_at', 'DESC').skip(skip).take(limit);

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

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

  /**
   * Buscar por ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<Proposal | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId, deletedAt: undefined },
      relations: ['tender', 'vendor'],
    });
  }

  /**
   * Buscar todas las propuestas de una licitacion
   */
  async findByTender(ctx: ServiceContext, tenderId: string): Promise<Proposal[]> {
    return this.repository.find({
      where: {
        tenderId,
        tenantId: ctx.tenantId,
        deletedAt: undefined,
      },
      relations: ['vendor'],
      order: { createdAt: 'DESC' },
    });
  }

  /**
   * Crear propuesta
   * @throws Error si la licitacion no esta en status de recepcion
   */
  async create(ctx: ServiceContext, dto: CreateProposalDto): Promise<Proposal> {
    // Validar que la licitacion existe y esta recibiendo propuestas
    const tender = await this.tenderRepository.findOne({
      where: {
        id: dto.tenderId,
        tenantId: ctx.tenantId,
        deletedAt: undefined,
      },
    });

    if (!tender) {
      throw new Error('Tender not found');
    }

    const receivingStatuses = ['published', 'receiving'];
    if (!receivingStatuses.includes(tender.status)) {
      throw new Error('Tender is not currently receiving proposals. Status must be "published" or "receiving"');
    }

    const proposal = this.repository.create({
      tenantId: ctx.tenantId,
      tenderId: dto.tenderId,
      vendorId: dto.vendorId,
      proposedAmount: dto.proposedAmount.toString(),
      proposedScheduleDays: dto.proposedScheduleDays,
      technicalProposalUrl: dto.technicalProposalUrl,
      economicProposalUrl: dto.economicProposalUrl,
      submittedAt: dto.submittedAt || new Date(),
      status: 'received' as ProposalStatus,
      createdById: ctx.userId,
      updatedById: ctx.userId,
    });

    return this.repository.save(proposal);
  }

  /**
   * Actualizar propuesta
   */
  async update(ctx: ServiceContext, id: string, dto: UpdateProposalDto): Promise<Proposal | null> {
    const proposal = await this.findById(ctx, id);
    if (!proposal) return null;

    if (dto.proposedAmount !== undefined) proposal.proposedAmount = dto.proposedAmount.toString();
    if (dto.proposedScheduleDays !== undefined) proposal.proposedScheduleDays = dto.proposedScheduleDays;
    if (dto.technicalProposalUrl !== undefined) proposal.technicalProposalUrl = dto.technicalProposalUrl;
    if (dto.economicProposalUrl !== undefined) proposal.economicProposalUrl = dto.economicProposalUrl;
    if (dto.status !== undefined) proposal.status = dto.status;
    if (ctx.userId) {
      proposal.updatedById = ctx.userId;
    }

    return this.repository.save(proposal);
  }

  /**
   * Evaluar propuesta - asigna puntuaciones tecnica y economica
   */
  async evaluate(
    ctx: ServiceContext,
    id: string,
    dto: EvaluateProposalDto
  ): Promise<Proposal> {
    const proposal = await this.findById(ctx, id);
    if (!proposal) {
      throw new Error('Proposal not found');
    }

    // Calcular puntuacion total (promedio simple, puede ajustarse con pesos)
    const totalScore = (dto.technicalScore + dto.economicScore) / 2;

    proposal.technicalScore = dto.technicalScore;
    proposal.economicScore = dto.economicScore;
    proposal.totalScore = totalScore;
    proposal.status = 'qualified';
    if (ctx.userId) {
      proposal.updatedById = ctx.userId;
    }

    return this.repository.save(proposal);
  }

  /**
   * Descalificar propuesta
   */
  async disqualify(ctx: ServiceContext, id: string, _reason: string): Promise<Proposal> {
    const proposal = await this.findById(ctx, id);
    if (!proposal) {
      throw new Error('Proposal not found');
    }

    proposal.status = 'disqualified';
    if (ctx.userId) {
      proposal.updatedById = ctx.userId;
    }

    return this.repository.save(proposal);
  }

  /**
   * Comparar propuestas de una licitacion
   * Retorna propuestas ordenadas por puntuacion total (ranking)
   */
  async compareProposals(ctx: ServiceContext, tenderId: string): Promise<RankedProposal[]> {
    const proposals = await this.repository.find({
      where: {
        tenderId,
        tenantId: ctx.tenantId,
        deletedAt: undefined,
      },
      relations: ['vendor'],
    });

    // Filtrar solo las que tienen puntuacion y estan calificadas
    const scoredProposals = proposals.filter(
      p => p.totalScore !== undefined && p.totalScore !== null && p.status === 'qualified'
    );

    // Ordenar por puntuacion total descendente
    scoredProposals.sort((a, b) => {
      const scoreA = Number(a.totalScore) || 0;
      const scoreB = Number(b.totalScore) || 0;
      return scoreB - scoreA;
    });

    // Asignar rankings
    const rankedProposals: RankedProposal[] = scoredProposals.map((proposal, index) => ({
      ...proposal,
      rank: index + 1,
      totalScore: Number(proposal.totalScore) || 0,
    }));

    return rankedProposals;
  }

  /**
   * Soft delete
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.update(
      { id, tenantId: ctx.tenantId },
      { deletedAt: new Date(), updatedById: ctx.userId }
    );
    return (result.affected || 0) > 0;
  }
}
