/**
 * RetencionService - Gestión de Retenciones de Estimaciones
 *
 * Gestiona retenciones aplicadas a estimaciones: garantía, impuestos, penalizaciones.
 *
 * @module Estimates
 */

import { Repository } from 'typeorm';
import { BaseService, ServiceContext, PaginatedResult } from '../../../shared/services/base.service';
import { Retencion, RetentionType } from '../entities/retencion.entity';

export interface CreateRetencionDto {
  estimacionId: string;
  retentionType: RetentionType;
  description: string;
  percentage?: number;
  amount: number;
  releaseDate?: Date;
  notes?: string;
}

export interface ReleaseRetencionDto {
  releasedAmount: number;
  notes?: string;
}

export interface RetencionFilters {
  estimacionId?: string;
  contratoId?: string;
  retentionType?: RetentionType;
  isReleased?: boolean;
  dateFrom?: Date;
  dateTo?: Date;
}

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

  /**
   * Obtener retenciones por estimación
   */
  async findByEstimacion(
    ctx: ServiceContext,
    estimacionId: string
  ): Promise<Retencion[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        estimacionId,
        deletedAt: null,
      } as any,
      order: { createdAt: 'DESC' },
    });
  }

  /**
   * Crear retención
   */
  async createRetencion(
    ctx: ServiceContext,
    data: CreateRetencionDto
  ): Promise<Retencion> {
    return this.create(ctx, {
      ...data,
      releasedAmount: null,
      releasedAt: null,
    });
  }

  /**
   * Obtener retenciones con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: RetencionFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<Retencion>> {
    const qb = this.repository
      .createQueryBuilder('r')
      .leftJoin('r.estimacion', 'e')
      .where('r.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('r.deleted_at IS NULL');

    if (filters.estimacionId) {
      qb.andWhere('r.estimacion_id = :estimacionId', { estimacionId: filters.estimacionId });
    }
    if (filters.contratoId) {
      qb.andWhere('e.contrato_id = :contratoId', { contratoId: filters.contratoId });
    }
    if (filters.retentionType) {
      qb.andWhere('r.retention_type = :retentionType', { retentionType: filters.retentionType });
    }
    if (filters.isReleased !== undefined) {
      if (filters.isReleased) {
        qb.andWhere('r.released_at IS NOT NULL');
      } else {
        qb.andWhere('r.released_at IS NULL');
      }
    }
    if (filters.dateFrom) {
      qb.andWhere('r.created_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('r.created_at <= :dateTo', { dateTo: filters.dateTo });
    }

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

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

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

  /**
   * Liberar retención
   */
  async releaseRetencion(
    ctx: ServiceContext,
    id: string,
    data: ReleaseRetencionDto
  ): Promise<Retencion | null> {
    const retencion = await this.findById(ctx, id);
    if (!retencion) {
      return null;
    }

    if (retencion.releasedAt) {
      throw new Error('Retention already released');
    }

    if (data.releasedAmount > retencion.amount) {
      throw new Error('Released amount cannot exceed retention amount');
    }

    return this.update(ctx, id, {
      releasedAmount: data.releasedAmount,
      releasedAt: new Date(),
      notes: data.notes ? `${retencion.notes || ''}\nLiberación: ${data.notes}` : retencion.notes,
    });
  }

  /**
   * Obtener totales por contrato
   */
  async getTotalByContrato(ctx: ServiceContext, contratoId: string): Promise<RetencionTotals> {
    const result = await this.repository
      .createQueryBuilder('r')
      .leftJoin('r.estimacion', 'e')
      .select([
        'SUM(r.amount) as total_retained',
        'SUM(COALESCE(r.released_amount, 0)) as total_released',
      ])
      .where('r.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('e.contrato_id = :contratoId', { contratoId })
      .andWhere('r.deleted_at IS NULL')
      .getRawOne();

    const totalRetained = parseFloat(result?.total_retained || '0');
    const totalReleased = parseFloat(result?.total_released || '0');

    return {
      totalRetained,
      totalReleased,
      totalPending: totalRetained - totalReleased,
    };
  }

  /**
   * Estadísticas de retenciones
   */
  async getStats(ctx: ServiceContext, filters?: RetencionFilters): Promise<RetencionStats> {
    const qb = this.repository
      .createQueryBuilder('r')
      .where('r.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('r.deleted_at IS NULL');

    if (filters?.contratoId) {
      qb.leftJoin('r.estimacion', 'e')
        .andWhere('e.contrato_id = :contratoId', { contratoId: filters.contratoId });
    }

    const retenciones = await qb.getMany();

    const byType = new Map<RetentionType, { count: number; amount: number; released: number }>();

    let totalRetained = 0;
    let totalReleased = 0;
    let pendingCount = 0;
    let releasedCount = 0;

    retenciones.forEach((r) => {
      totalRetained += r.amount;
      totalReleased += r.releasedAmount || 0;

      if (r.releasedAt) {
        releasedCount++;
      } else {
        pendingCount++;
      }

      const existing = byType.get(r.retentionType) || { count: 0, amount: 0, released: 0 };
      byType.set(r.retentionType, {
        count: existing.count + 1,
        amount: existing.amount + r.amount,
        released: existing.released + (r.releasedAmount || 0),
      });
    });

    return {
      totalRetained,
      totalReleased,
      totalPending: totalRetained - totalReleased,
      totalCount: retenciones.length,
      pendingCount,
      releasedCount,
      byType: Array.from(byType.entries()).map(([type, data]) => ({
        type,
        ...data,
        pending: data.amount - data.released,
      })),
    };
  }
}

export interface RetencionTotals {
  totalRetained: number;
  totalReleased: number;
  totalPending: number;
}

export interface RetencionStats {
  totalRetained: number;
  totalReleased: number;
  totalPending: number;
  totalCount: number;
  pendingCount: number;
  releasedCount: number;
  byType: {
    type: RetentionType;
    count: number;
    amount: number;
    released: number;
    pending: number;
  }[];
}
