/**
 * OpportunityService - Gestión de Oportunidades de Negocio
 *
 * CRUD y lógica de negocio para el pipeline de oportunidades.
 *
 * @module Bidding
 */

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

export interface CreateOpportunityDto {
  code: string;
  name: string;
  description?: string;
  source: OpportunitySource;
  projectType: ProjectType;
  clientName: string;
  clientContact?: string;
  clientEmail?: string;
  clientPhone?: string;
  clientType?: string;
  location?: string;
  state?: string;
  city?: string;
  estimatedValue?: number;
  currency?: string;
  constructionAreaM2?: number;
  landAreaM2?: number;
  identificationDate: Date;
  deadlineDate?: Date;
  expectedAwardDate?: Date;
  expectedStartDate?: Date;
  expectedDurationMonths?: number;
  winProbability?: number;
  requiresBond?: boolean;
  requiresExperience?: boolean;
  minimumExperienceYears?: number;
  minimumCapital?: number;
  requiredCertifications?: string[];
  assignedToId?: string;
  sourceUrl?: string;
  sourceReference?: string;
  notes?: string;
  metadata?: Record<string, any>;
}

export interface UpdateOpportunityDto extends Partial<CreateOpportunityDto> {
  status?: OpportunityStatus;
  priority?: OpportunityPriority;
  lossReason?: string;
  winFactors?: string;
}

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

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

  /**
   * Crear oportunidad
   */
  async create(ctx: ServiceContext, data: CreateOpportunityDto): Promise<Opportunity> {
    const weightedValue = data.estimatedValue && data.winProbability
      ? data.estimatedValue * (data.winProbability / 100)
      : undefined;

    const opportunity = this.repository.create({
      tenantId: ctx.tenantId,
      code: data.code,
      name: data.name,
      description: data.description,
      source: data.source,
      projectType: data.projectType,
      clientName: data.clientName,
      clientContact: data.clientContact,
      clientEmail: data.clientEmail,
      clientPhone: data.clientPhone,
      clientType: data.clientType,
      location: data.location,
      state: data.state,
      city: data.city,
      estimatedValue: data.estimatedValue,
      currency: data.currency || 'MXN',
      constructionAreaM2: data.constructionAreaM2,
      landAreaM2: data.landAreaM2,
      identificationDate: data.identificationDate,
      deadlineDate: data.deadlineDate,
      expectedAwardDate: data.expectedAwardDate,
      expectedStartDate: data.expectedStartDate,
      expectedDurationMonths: data.expectedDurationMonths,
      winProbability: data.winProbability || 0,
      requiresBond: data.requiresBond || false,
      requiresExperience: data.requiresExperience || false,
      minimumExperienceYears: data.minimumExperienceYears,
      minimumCapital: data.minimumCapital,
      requiredCertifications: data.requiredCertifications,
      assignedToId: data.assignedToId,
      sourceUrl: data.sourceUrl,
      sourceReference: data.sourceReference,
      notes: data.notes,
      metadata: data.metadata,
      status: 'identified',
      priority: 'medium',
      weightedValue,
      createdBy: ctx.userId,
      updatedBy: 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: ['assignedTo', 'bids'],
    });
  }

  /**
   * Buscar con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: OpportunityFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<Opportunity>> {
    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.assignedToId) {
      qb.andWhere('o.assigned_to_id = :assignedToId', { assignedToId: filters.assignedToId });
    }
    if (filters.clientName) {
      qb.andWhere('o.client_name ILIKE :clientName', { clientName: `%${filters.clientName}%` });
    }
    if (filters.state) {
      qb.andWhere('o.state = :state', { state: filters.state });
    }
    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,
      meta: {
        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;

    // Recalcular weighted value si cambian los factores
    let weightedValue = opportunity.weightedValue;
    const estimatedValue = data.estimatedValue ?? opportunity.estimatedValue;
    const winProbability = data.winProbability ?? opportunity.winProbability;
    if (estimatedValue && winProbability) {
      weightedValue = estimatedValue * (winProbability / 100);
    }

    Object.assign(opportunity, {
      ...data,
      weightedValue,
      updatedBy: 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 (status === 'lost' && reason) {
      opportunity.lossReason = reason;
    } else if (status === 'won' && reason) {
      opportunity.winFactors = reason;
    }
    opportunity.updatedBy = 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(o.estimated_value)', 'totalValue')
      .addSelect('SUM(o.weighted_value)', 'weightedValue')
      .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,
      weightedValue: parseFloat(r.weightedValue) || 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(['identified', 'qualified', 'pursuing']),
        deadlineDate: Between(now, future),
      },
      relations: ['assignedTo'],
      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.identification_date 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.identification_date 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.identification_date BETWEEN :startDate AND :endDate', { startDate, endDate })
      .groupBy('o.source')
      .getRawMany();

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

    const wonCount = byStatus.find((s) => s.status === 'won')?.count || 0;
    const lostCount = byStatus.find((s) => s.status === 'lost')?.count || 0;
    const closedCount = parseInt(wonCount) + parseInt(lostCount);

    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,
      weightedValue: parseFloat(valueStats?.weightedValue) || 0,
      avgValue: parseFloat(valueStats?.avgValue) || 0,
      winRate: closedCount > 0 ? (parseInt(wonCount) / 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(), updatedBy: ctx.userId }
    );
    return (result.affected || 0) > 0;
  }
}

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

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