/**
 * TenderService - Gestion de Licitaciones/Convocatorias
 *
 * CRUD y logica de negocio para procesos de licitacion.
 * Gestiona el ciclo desde publicacion hasta adjudicacion y conversion a proyecto.
 *
 * @module Bidding (MAI-018)
 */

import { Repository } from 'typeorm';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { Tender, TenderType, TenderStatus } from '../entities/tender.entity';
import { Opportunity } from '../entities/opportunity.entity';

// DTOs
export interface CreateTenderDto {
  opportunityId: string;
  title: string;
  description?: string;
  type?: TenderType;
  referenceAmount?: number;
  publicationDate?: Date;
  clarificationMeetingDate?: Date;
  proposalDeadline: Date;
  awardDate?: Date;
  contractDurationDays?: number;
}

export interface UpdateTenderDto extends Partial<Omit<CreateTenderDto, 'opportunityId'>> {
  status?: TenderStatus;
}

export interface TenderFilters {
  opportunityId?: string;
  type?: TenderType;
  status?: TenderStatus | TenderStatus[];
  dateFrom?: Date;
  dateTo?: Date;
  search?: string;
  page?: number;
  limit?: number;
}

export interface TenderStats {
  total: number;
  byStatus: { status: string; count: number }[];
  byType: { type: string; count: number }[];
  totalBudget: number;
  winRate: number;
}

export class TenderService {
  constructor(
    private readonly repository: Repository<Tender>,
    private readonly opportunityRepository: Repository<Opportunity>
  ) {}

  /**
   * Genera numero automatico para licitacion: LIC-YYYY-NNN
   */
  private async generateNumber(ctx: ServiceContext): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `LIC-${year}-`;

    const lastTender = await this.repository
      .createQueryBuilder('t')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.number LIKE :prefix', { prefix: `${prefix}%` })
      .orderBy('t.number', 'DESC')
      .getOne();

    let sequence = 1;
    if (lastTender) {
      const lastSequence = parseInt(lastTender.number.replace(prefix, ''), 10);
      if (!isNaN(lastSequence)) {
        sequence = lastSequence + 1;
      }
    }

    return `${prefix}${sequence.toString().padStart(3, '0')}`;
  }

  /**
   * Crear licitacion
   * @throws Error si la oportunidad no existe o no esta en status 'go'
   */
  async create(ctx: ServiceContext, dto: CreateTenderDto): Promise<Tender> {
    // Validar que la oportunidad existe y esta en status 'go'
    const opportunity = await this.opportunityRepository.findOne({
      where: {
        id: dto.opportunityId,
        tenantId: ctx.tenantId,
        deletedAt: undefined
      },
    });

    if (!opportunity) {
      throw new Error('Opportunity not found');
    }

    if (opportunity.status !== 'go') {
      throw new Error('Opportunity must be in "go" status to create a tender');
    }

    const tenderNumber = await this.generateNumber(ctx);

    const tender = this.repository.create({
      tenantId: ctx.tenantId,
      opportunityId: dto.opportunityId,
      number: tenderNumber,
      title: dto.title,
      description: dto.description,
      type: dto.type || 'public',
      referenceAmount: dto.referenceAmount?.toString(),
      publicationDate: dto.publicationDate,
      clarificationMeetingDate: dto.clarificationMeetingDate,
      proposalDeadline: dto.proposalDeadline,
      awardDate: dto.awardDate,
      contractDurationDays: dto.contractDurationDays,
      status: 'draft' as TenderStatus,
      createdById: ctx.userId,
      updatedById: ctx.userId,
    });

    return this.repository.save(tender);
  }

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

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

    if (filters.opportunityId) {
      qb.andWhere('t.opportunity_id = :opportunityId', { opportunityId: filters.opportunityId });
    }
    if (filters.type) {
      qb.andWhere('t.type = :type', { type: filters.type });
    }
    if (filters.status) {
      if (Array.isArray(filters.status)) {
        qb.andWhere('t.status IN (:...statuses)', { statuses: filters.status });
      } else {
        qb.andWhere('t.status = :status', { status: filters.status });
      }
    }
    if (filters.dateFrom) {
      qb.andWhere('t.proposal_deadline >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('t.proposal_deadline <= :dateTo', { dateTo: filters.dateTo });
    }
    if (filters.search) {
      qb.andWhere(
        '(t.title ILIKE :search OR t.number ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    const skip = (page - 1) * limit;
    qb.orderBy('t.proposal_deadline', 'ASC').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<Tender | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId, deletedAt: undefined },
      relations: ['opportunity', 'proposals', 'calendarEvents', 'documents'],
    });
  }

  /**
   * Buscar por numero de licitacion
   */
  async findByNumber(ctx: ServiceContext, number: string): Promise<Tender | null> {
    return this.repository.findOne({
      where: {
        number,
        tenantId: ctx.tenantId,
        deletedAt: undefined
      },
      relations: ['opportunity'],
    });
  }

  /**
   * Actualizar licitacion
   */
  async update(ctx: ServiceContext, id: string, dto: UpdateTenderDto): Promise<Tender | null> {
    const tender = await this.findById(ctx, id);
    if (!tender) return null;

    if (dto.title !== undefined) tender.title = dto.title;
    if (dto.description !== undefined) tender.description = dto.description;
    if (dto.type !== undefined) tender.type = dto.type;
    if (dto.referenceAmount !== undefined) tender.referenceAmount = dto.referenceAmount.toString();
    if (dto.publicationDate !== undefined) tender.publicationDate = dto.publicationDate;
    if (dto.clarificationMeetingDate !== undefined) tender.clarificationMeetingDate = dto.clarificationMeetingDate;
    if (dto.proposalDeadline !== undefined) tender.proposalDeadline = dto.proposalDeadline;
    if (dto.awardDate !== undefined) tender.awardDate = dto.awardDate;
    if (dto.contractDurationDays !== undefined) tender.contractDurationDays = dto.contractDurationDays;
    if (dto.status !== undefined) tender.status = dto.status;
    if (ctx.userId) {
      tender.updatedById = ctx.userId;
    }

    return this.repository.save(tender);
  }

  /**
   * 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;
  }

  /**
   * Publicar licitacion - cambia status a 'published'
   */
  async publish(ctx: ServiceContext, id: string): Promise<Tender> {
    const tender = await this.findById(ctx, id);
    if (!tender) {
      throw new Error('Tender not found');
    }

    if (tender.status !== 'draft') {
      throw new Error('Only draft tenders can be published');
    }

    tender.status = 'published';
    tender.publicationDate = new Date();
    if (ctx.userId) {
      tender.updatedById = ctx.userId;
    }

    return this.repository.save(tender);
  }

  /**
   * Adjudicar ganador
   * @param tenderId ID de la licitacion
   * @param proposalId ID de la propuesta ganadora
   */
  async awardWinner(
    ctx: ServiceContext,
    tenderId: string,
    proposalId: string
  ): Promise<Tender> {
    const tender = await this.findById(ctx, tenderId);
    if (!tender) {
      throw new Error('Tender not found');
    }

    tender.status = 'awarded';
    tender.awardDate = new Date();
    tender.winnerId = proposalId;
    if (ctx.userId) {
      tender.updatedById = ctx.userId;
    }

    return this.repository.save(tender);
  }

  /**
   * Convertir licitacion ganada a proyecto
   */
  async convertToProject(ctx: ServiceContext, tenderId: string): Promise<{ tender: Tender; projectId: string }> {
    const tender = await this.findById(ctx, tenderId);
    if (!tender) {
      throw new Error('Tender not found');
    }

    if (tender.status !== 'awarded') {
      throw new Error('Only awarded tenders can be converted to projects');
    }

    tender.status = 'converting';
    if (ctx.userId) {
      tender.updatedById = ctx.userId;
    }

    // Generar ID de proyecto placeholder (la creacion real la hace projects module)
    const projectId = `PRJ-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;

    await this.repository.save(tender);

    return { tender, projectId };
  }

  /**
   * Obtener estadisticas de licitaciones
   */
  async getStats(ctx: ServiceContext, year?: number): Promise<TenderStats> {
    const currentYear = year || new Date().getFullYear();
    const startDate = new Date(currentYear, 0, 1);
    const endDate = new Date(currentYear, 11, 31);

    const total = await this.repository
      .createQueryBuilder('t')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.deleted_at IS NULL')
      .andWhere('t.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .getCount();

    const byStatus = await this.repository
      .createQueryBuilder('t')
      .select('t.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.deleted_at IS NULL')
      .andWhere('t.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .groupBy('t.status')
      .getRawMany();

    const byType = await this.repository
      .createQueryBuilder('t')
      .select('t.type', 'type')
      .addSelect('COUNT(*)', 'count')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.deleted_at IS NULL')
      .andWhere('t.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .groupBy('t.type')
      .getRawMany();

    const valueStats = await this.repository
      .createQueryBuilder('t')
      .select('SUM(CAST(t.reference_amount AS DECIMAL))', 'totalBudget')
      .where('t.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('t.deleted_at IS NULL')
      .andWhere('t.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .getRawOne();

    const awardedCount = byStatus.find((s) => s.status === 'awarded')?.count || 0;
    const cancelledCount = byStatus.find((s) => s.status === 'cancelled')?.count || 0;
    const closedCount = parseInt(awardedCount) + parseInt(cancelledCount);

    return {
      total,
      byStatus: byStatus.map((r) => ({ status: r.status, count: parseInt(r.count) })),
      byType: byType.map((r) => ({ type: r.type, count: parseInt(r.count) })),
      totalBudget: parseFloat(valueStats?.totalBudget) || 0,
      winRate: closedCount > 0 ? (parseInt(awardedCount) / closedCount) * 100 : 0,
    };
  }
}
