/**
 * AnticipoService - Gestión de Anticipos de Obra
 *
 * Gestiona anticipos otorgados a subcontratistas: inicial, avance, materiales.
 *
 * @module Estimates
 */

import { Repository } from 'typeorm';
import { BaseService, ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { Anticipo, AdvanceType } from '../entities/anticipo.entity';

export interface CreateAnticipoDto {
  contratoId: string;
  advanceType: AdvanceType;
  advanceNumber: string;
  advanceDate: Date;
  grossAmount: number;
  taxAmount: number;
  netAmount: number;
  amortizationPercentage?: number;
  notes?: string;
}

export interface AnticipoFilters {
  contratoId?: string;
  advanceType?: AdvanceType;
  isFullyAmortized?: boolean;
  startDate?: Date;
  endDate?: Date;
  approvedById?: string;
}

export interface AnticipoStats {
  totalAnticipated: number;
  totalAmortized: number;
  pendingAmortization: number;
  totalApproved: number;
  totalPending: number;
  totalPaid: number;
  byType: {
    type: AdvanceType;
    count: number;
    totalAmount: number;
  }[];
}

export class AnticipoService extends BaseService<Anticipo> {
  constructor(repository: Repository<Anticipo>) {
    super(repository);
  }

  /**
   * Busca anticipos por contrato
   */
  async findByContrato(
    ctx: ServiceContext,
    contratoId: string,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<Anticipo>> {
    const qb = this.repository
      .createQueryBuilder('a')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.contrato_id = :contratoId', { contratoId })
      .andWhere('a.deleted_at IS NULL');

    const skip = (page - 1) * limit;
    qb.orderBy('a.advance_date', 'DESC')
      .addOrderBy('a.advance_number', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  /**
   * Crea un nuevo anticipo
   */
  async createAnticipo(
    ctx: ServiceContext,
    dto: CreateAnticipoDto
  ): Promise<Anticipo> {
    // Validar que el monto neto sea correcto
    const expectedNet = dto.grossAmount - dto.taxAmount;
    if (Math.abs(expectedNet - dto.netAmount) > 0.01) {
      throw new Error('El monto neto no coincide con el cálculo (bruto - impuestos)');
    }

    return this.create(ctx, {
      ...dto,
      amortizedAmount: 0,
      isFullyAmortized: false,
    });
  }

  /**
   * Aprueba un anticipo
   */
  async approveAnticipo(
    ctx: ServiceContext,
    id: string,
    approvedById: string,
    notes?: string
  ): Promise<Anticipo> {
    const anticipo = await this.findById(ctx, id);
    if (!anticipo) {
      throw new Error('Anticipo no encontrado');
    }

    if (anticipo.approvedAt) {
      throw new Error('El anticipo ya está aprobado');
    }

    const updateData: Partial<Anticipo> = {
      approvedAt: new Date(),
      approvedById,
    };

    if (notes) {
      updateData.notes = anticipo.notes
        ? `${anticipo.notes}\n\nAprobación: ${notes}`
        : `Aprobación: ${notes}`;
    }

    const updated = await this.update(ctx, id, updateData);
    if (!updated) {
      throw new Error('Error al aprobar anticipo');
    }

    return updated;
  }

  /**
   * Marca un anticipo como pagado
   */
  async markPaid(
    ctx: ServiceContext,
    id: string,
    paymentReference: string,
    paidAt?: Date
  ): Promise<Anticipo> {
    const anticipo = await this.findById(ctx, id);
    if (!anticipo) {
      throw new Error('Anticipo no encontrado');
    }

    if (!anticipo.approvedAt) {
      throw new Error('El anticipo debe estar aprobado antes de marcarse como pagado');
    }

    if (anticipo.paidAt) {
      throw new Error('El anticipo ya está marcado como pagado');
    }

    const updated = await this.update(ctx, id, {
      paidAt: paidAt || new Date(),
      paymentReference,
    });

    if (!updated) {
      throw new Error('Error al marcar anticipo como pagado');
    }

    return updated;
  }

  /**
   * Obtiene estadísticas de anticipos
   */
  async getStats(
    ctx: ServiceContext,
    filters?: AnticipoFilters
  ): Promise<AnticipoStats> {
    const qb = this.repository
      .createQueryBuilder('a')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.deleted_at IS NULL');

    if (filters?.contratoId) {
      qb.andWhere('a.contrato_id = :contratoId', { contratoId: filters.contratoId });
    }
    if (filters?.advanceType) {
      qb.andWhere('a.advance_type = :advanceType', { advanceType: filters.advanceType });
    }
    if (filters?.isFullyAmortized !== undefined) {
      qb.andWhere('a.is_fully_amortized = :isFullyAmortized', {
        isFullyAmortized: filters.isFullyAmortized,
      });
    }
    if (filters?.startDate) {
      qb.andWhere('a.advance_date >= :startDate', { startDate: filters.startDate });
    }
    if (filters?.endDate) {
      qb.andWhere('a.advance_date <= :endDate', { endDate: filters.endDate });
    }
    if (filters?.approvedById) {
      qb.andWhere('a.approved_by = :approvedById', { approvedById: filters.approvedById });
    }

    const anticipos = await qb.getMany();

    const stats: AnticipoStats = {
      totalAnticipated: 0,
      totalAmortized: 0,
      pendingAmortization: 0,
      totalApproved: 0,
      totalPending: 0,
      totalPaid: 0,
      byType: [],
    };

    const typeStats = new Map<AdvanceType, { count: number; totalAmount: number }>();

    anticipos.forEach((anticipo) => {
      const netAmount = Number(anticipo.netAmount) || 0;
      const amortizedAmount = Number(anticipo.amortizedAmount) || 0;

      stats.totalAnticipated += netAmount;
      stats.totalAmortized += amortizedAmount;
      stats.pendingAmortization += netAmount - amortizedAmount;

      if (anticipo.approvedAt) {
        stats.totalApproved += netAmount;
      } else {
        stats.totalPending += netAmount;
      }

      if (anticipo.paidAt) {
        stats.totalPaid += netAmount;
      }

      const existing = typeStats.get(anticipo.advanceType) || { count: 0, totalAmount: 0 };
      typeStats.set(anticipo.advanceType, {
        count: existing.count + 1,
        totalAmount: existing.totalAmount + netAmount,
      });
    });

    stats.byType = Array.from(typeStats.entries()).map(([type, data]) => ({
      type,
      count: data.count,
      totalAmount: data.totalAmount,
    }));

    return stats;
  }

  /**
   * Busca anticipos con filtros y paginación
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: AnticipoFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<Anticipo>> {
    const qb = this.repository
      .createQueryBuilder('a')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.deleted_at IS NULL');

    if (filters.contratoId) {
      qb.andWhere('a.contrato_id = :contratoId', { contratoId: filters.contratoId });
    }
    if (filters.advanceType) {
      qb.andWhere('a.advance_type = :advanceType', { advanceType: filters.advanceType });
    }
    if (filters.isFullyAmortized !== undefined) {
      qb.andWhere('a.is_fully_amortized = :isFullyAmortized', {
        isFullyAmortized: filters.isFullyAmortized,
      });
    }
    if (filters.startDate) {
      qb.andWhere('a.advance_date >= :startDate', { startDate: filters.startDate });
    }
    if (filters.endDate) {
      qb.andWhere('a.advance_date <= :endDate', { endDate: filters.endDate });
    }
    if (filters.approvedById) {
      qb.andWhere('a.approved_by = :approvedById', { approvedById: filters.approvedById });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('a.advance_date', 'DESC')
      .addOrderBy('a.advance_number', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  /**
   * Actualiza el monto amortizado de un anticipo
   */
  async updateAmortization(
    ctx: ServiceContext,
    id: string,
    amountToAmortize: number
  ): Promise<Anticipo> {
    const anticipo = await this.findById(ctx, id);
    if (!anticipo) {
      throw new Error('Anticipo no encontrado');
    }

    if (anticipo.isFullyAmortized) {
      throw new Error('El anticipo ya está completamente amortizado');
    }

    const currentAmortized = Number(anticipo.amortizedAmount) || 0;
    const netAmount = Number(anticipo.netAmount) || 0;
    const newAmortizedAmount = currentAmortized + amountToAmortize;

    if (newAmortizedAmount > netAmount) {
      throw new Error('El monto a amortizar excede el saldo del anticipo');
    }

    const isFullyAmortized = Math.abs(newAmortizedAmount - netAmount) < 0.01;

    const updated = await this.update(ctx, id, {
      amortizedAmount: newAmortizedAmount,
      isFullyAmortized,
    });

    if (!updated) {
      throw new Error('Error al actualizar amortización');
    }

    return updated;
  }
}
