/**
 * OpportunityService - Gestión de Oportunidades de Negocio
 *
 * CRUD y lógica de negocio para el pipeline de oportunidades.
 * Gestiona el ciclo de vida completo desde identificación hasta decisión go/no-go.
 *
 * @module Bidding (MAI-018)
 */

import { Repository, In, Between } from 'typeorm';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { Opportunity, OpportunitySource, OpportunityStatus, OpportunityPriority } from '../entities/opportunity.entity';

export interface CreateOpportunityDto {
  title: string;
  description?: string;
  source: OpportunitySource;
  projectType: string;
  clientName: string;
  location?: string;
  estimatedAmount?: number;
  estimatedUnits?: number;
  priority?: OpportunityPriority;
  deadlineDate: Date;
}

export interface GoNoGoDecisionDto {
  decision: 'go' | 'no_go';
  reason: string;
}

export interface UpdateOpportunityDto extends Partial<CreateOpportunityDto> {
  status?: OpportunityStatus;
}

export interface OpportunityFilters {
  status?: OpportunityStatus | OpportunityStatus[];
  source?: OpportunitySource;
  projectType?: string;
  priority?: OpportunityPriority;
  clientName?: string;
  dateFrom?: Date;
  dateTo?: Date;
  minValue?: number;
  maxValue?: number;
  search?: string;
  page?: number;
  limit?: number;
}

export class OpportunityService {
  constructor(private readonly repository: Repository<Opportunity>) {}

  /**
   * Genera código automático para oportunidad: OPP-YYYY-NNN
   */
  private async generateCode(ctx: ServiceContext): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `OPP-${year}-`;

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

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

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

  /**
   * Crear oportunidad con código auto-generado
   */
  async create(ctx: ServiceContext, data: CreateOpportunityDto): Promise<Opportunity> {
    const code = await this.generateCode(ctx);

    const opportunity = this.repository.create({
      tenantId: ctx.tenantId,
      code,
      title: data.title,
      description: data.description,
      source: data.source,
      projectType: data.projectType,
      clientName: data.clientName,
      location: data.location,
      estimatedAmount: data.estimatedAmount?.toString(),
      estimatedUnits: data.estimatedUnits,
      deadlineDate: data.deadlineDate,
      status: 'registered' as OpportunityStatus,
      priority: data.priority || 'medium',
      createdById: ctx.userId,
      updatedById: ctx.userId,
    });

    return this.repository.save(opportunity);
  }

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

  /**
   * Buscar por código
   */
  async findByCode(ctx: ServiceContext, code: string): Promise<Opportunity | null> {
    return this.repository.findOne({
      where: { code, tenantId: ctx.tenantId, deletedAt: undefined },
      relations: ['tenders'],
    });
  }

  /**
   * Buscar con filtros y paginación
   */
  async findAll(
    ctx: ServiceContext,
    filters: OpportunityFilters = {}
  ): Promise<PaginatedResult<Opportunity>> {
    const page = filters.page || 1;
    const limit = filters.limit || 20;
    const qb = this.repository
      .createQueryBuilder('o')
      .leftJoinAndSelect('o.assignedTo', 'u')
      .where('o.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('o.deleted_at IS NULL');

    if (filters.status) {
      if (Array.isArray(filters.status)) {
        qb.andWhere('o.status IN (:...statuses)', { statuses: filters.status });
      } else {
        qb.andWhere('o.status = :status', { status: filters.status });
      }
    }
    if (filters.source) {
      qb.andWhere('o.source = :source', { source: filters.source });
    }
    if (filters.projectType) {
      qb.andWhere('o.project_type = :projectType', { projectType: filters.projectType });
    }
    if (filters.priority) {
      qb.andWhere('o.priority = :priority', { priority: filters.priority });
    }
    if (filters.clientName) {
      qb.andWhere('o.client_name ILIKE :clientName', { clientName: `%${filters.clientName}%` });
    }
    if (filters.dateFrom) {
      qb.andWhere('o.identification_date >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('o.identification_date <= :dateTo', { dateTo: filters.dateTo });
    }
    if (filters.minValue !== undefined) {
      qb.andWhere('o.estimated_value >= :minValue', { minValue: filters.minValue });
    }
    if (filters.maxValue !== undefined) {
      qb.andWhere('o.estimated_value <= :maxValue', { maxValue: filters.maxValue });
    }
    if (filters.search) {
      qb.andWhere(
        '(o.name ILIKE :search OR o.code ILIKE :search OR o.client_name ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

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

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

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

  /**
   * Actualizar oportunidad
   */
  async update(ctx: ServiceContext, id: string, data: UpdateOpportunityDto): Promise<Opportunity | null> {
    const opportunity = await this.findById(ctx, id);
    if (!opportunity) return null;

    if (data.title !== undefined) opportunity.title = data.title;
    if (data.description !== undefined) opportunity.description = data.description;
    if (data.source !== undefined) opportunity.source = data.source;
    if (data.projectType !== undefined) opportunity.projectType = data.projectType;
    if (data.clientName !== undefined) opportunity.clientName = data.clientName;
    if (data.location !== undefined) opportunity.location = data.location;
    if (data.estimatedAmount !== undefined) opportunity.estimatedAmount = data.estimatedAmount.toString();
    if (data.estimatedUnits !== undefined) opportunity.estimatedUnits = data.estimatedUnits;
    if (data.priority !== undefined) opportunity.priority = data.priority;
    if (data.deadlineDate !== undefined) opportunity.deadlineDate = data.deadlineDate;
    if (data.status !== undefined) opportunity.status = data.status;
    if (ctx.userId) {
      opportunity.updatedById = ctx.userId;
    }

    return this.repository.save(opportunity);
  }

  /**
   * Cambiar estado
   */
  async changeStatus(
    ctx: ServiceContext,
    id: string,
    status: OpportunityStatus,
    reason?: string
  ): Promise<Opportunity | null> {
    const opportunity = await this.findById(ctx, id);
    if (!opportunity) return null;

    opportunity.status = status;
    if (reason) {
      opportunity.goDecisionReason = reason;
    }
    if (ctx.userId) {
      opportunity.updatedById = ctx.userId;
    }

    return this.repository.save(opportunity);
  }

  /**
   * Evaluar decisión Go/No-Go
   * @throws Error si el estado actual no es 'evaluating'
   */
  async evaluateGoNoGo(
    ctx: ServiceContext,
    id: string,
    dto: GoNoGoDecisionDto
  ): Promise<Opportunity> {
    const opportunity = await this.findById(ctx, id);
    if (!opportunity) {
      throw new Error('Opportunity not found');
    }

    // Validar que la oportunidad esté en fase de evaluación
    if (opportunity.status !== 'evaluating') {
      throw new Error('Opportunity must be in "evaluating" status to evaluate go/no-go decision');
    }

    // Registrar la decisión
    opportunity.goDecisionDate = new Date();
    opportunity.goDecisionReason = dto.reason;

    // Cambiar estado según decisión
    if (dto.decision === 'go') {
      opportunity.status = 'go';
    } else {
      opportunity.status = 'no_go';
    }

    if (ctx.userId) {
      opportunity.updatedById = ctx.userId;
    }

    return this.repository.save(opportunity);
  }

  /**
   * Obtener pipeline (agrupado por status)
   */
  async getPipeline(ctx: ServiceContext): Promise<PipelineData[]> {
    const result = await this.repository
      .createQueryBuilder('o')
      .select('o.status', 'status')
      .addSelect('COUNT(*)', 'count')
      .addSelect('SUM(CAST(o.estimated_amount AS DECIMAL))', 'totalValue')
      .where('o.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('o.deleted_at IS NULL')
      .groupBy('o.status')
      .getRawMany();

    return result.map((r) => ({
      status: r.status as OpportunityStatus,
      count: parseInt(r.count),
      totalValue: parseFloat(r.totalValue) || 0,
    }));
  }

  /**
   * Obtener oportunidades próximas a vencer
   */
  async getUpcomingDeadlines(ctx: ServiceContext, days = 7): Promise<Opportunity[]> {
    const now = new Date();
    const future = new Date();
    future.setDate(future.getDate() + days);

    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: undefined,
        status: In(['registered', 'evaluating', 'go', 'preparing']),
        deadlineDate: Between(now, future),
      },
      order: { deadlineDate: 'ASC' },
    });
  }

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

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

    const total = await baseQuery.getCount();

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

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

    const valueStats = await this.repository
      .createQueryBuilder('o')
      .select('SUM(CAST(o.estimated_amount AS DECIMAL))', 'totalValue')
      .addSelect('AVG(CAST(o.estimated_amount AS DECIMAL))', 'avgValue')
      .where('o.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('o.deleted_at IS NULL')
      .andWhere('o.created_at BETWEEN :startDate AND :endDate', { startDate, endDate })
      .getRawOne();

    const convertedCount = byStatus.find((s) => s.status === 'converted')?.count || 0;
    const noGoCount = byStatus.find((s) => s.status === 'no_go')?.count || 0;
    const closedCount = parseInt(convertedCount) + parseInt(noGoCount);

    return {
      year: currentYear,
      total,
      byStatus: byStatus.map((r) => ({ status: r.status, count: parseInt(r.count) })),
      bySource: bySource.map((r) => ({ source: r.source, count: parseInt(r.count) })),
      totalValue: parseFloat(valueStats?.totalValue) || 0,
      avgValue: parseFloat(valueStats?.avgValue) || 0,
      winRate: closedCount > 0 ? (parseInt(convertedCount) / closedCount) * 100 : 0,
    };
  }

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

export interface PipelineData {
  status: OpportunityStatus;
  count: number;
  totalValue: number;
}

export interface OpportunityStats {
  year: number;
  total: number;
  byStatus: { status: string; count: number }[];
  bySource: { source: string; count: number }[];
  totalValue: number;
  avgValue: number;
  winRate: number;
}
