/**
 * FondoGarantiaService - Gestión de Fondos de Garantía
 *
 * Gestiona fondos de garantía acumulados por contrato: retención y liberación.
 *
 * @module Estimates
 */

import { DataSource, Repository, IsNull } from 'typeorm';
import { FondoGarantia } from '../entities/fondo-garantia.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateFondoGarantiaDto {
  contratoId: string;
  accumulatedAmount?: number;
  releasedAmount?: number;
  releaseDate?: Date;
}

export interface ReleaseFondoDto {
  amount: number;
  releasedById: string;
  releaseDate?: Date;
  notes?: string;
}

export interface FondoGarantiaFilters {
  contratoId?: string;
  hasPending?: boolean;
  fullyReleased?: boolean;
}

export interface FondoGarantiaStats {
  totalAccumulated: number;
  totalReleased: number;
  totalPending: number;
  fondosCount: number;
  fondosPendingRelease: number;
  fondosFullyReleased: number;
}

export class FondoGarantiaService {
  private repository: Repository<FondoGarantia>;

  constructor(dataSource: DataSource) {
    this.repository = dataSource.getRepository(FondoGarantia);
  }

  /**
   * Busca un fondo de garantía por ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<FondoGarantia | null> {
    return this.repository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
    });
  }

  /**
   * Busca todos los fondos de garantía con paginación
   */
  async findAll(
    ctx: ServiceContext,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<FondoGarantia>> {
    const skip = (page - 1) * limit;

    const [data, total] = await this.repository.findAndCount({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
      order: { createdAt: 'DESC' },
      skip,
      take: limit,
    });

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

  /**
   * Crea un nuevo fondo de garantía
   */
  async create(
    ctx: ServiceContext,
    data: Partial<FondoGarantia>
  ): Promise<FondoGarantia> {
    const fondo = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
      createdById: ctx.userId ?? null,
    });

    return this.repository.save(fondo);
  }

  /**
   * Actualiza un fondo de garantía
   */
  async update(
    ctx: ServiceContext,
    id: string,
    data: Partial<FondoGarantia>
  ): Promise<FondoGarantia | null> {
    const fondo = await this.findById(ctx, id);
    if (!fondo) {
      return null;
    }

    Object.assign(fondo, data);
    fondo.updatedById = ctx.userId ?? null;

    return this.repository.save(fondo);
  }

  /**
   * Soft delete de un fondo de garantía
   */
  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const fondo = await this.findById(ctx, id);
    if (!fondo) {
      return false;
    }

    fondo.deletedAt = new Date();
    fondo.deletedById = ctx.userId ?? null;
    await this.repository.save(fondo);
    return true;
  }

  /**
   * Encuentra fondo de garantía por contrato
   */
  async findByContrato(
    ctx: ServiceContext,
    contratoId: string
  ): Promise<FondoGarantia | null> {
    return this.repository.findOne({
      where: {
        tenantId: ctx.tenantId,
        contratoId,
        deletedAt: IsNull(),
      },
    });
  }

  /**
   * Crea o actualiza un fondo de garantía
   */
  async createOrUpdate(
    ctx: ServiceContext,
    dto: CreateFondoGarantiaDto
  ): Promise<FondoGarantia> {
    const existing = await this.findByContrato(ctx, dto.contratoId);

    if (existing) {
      const updated = await this.update(ctx, existing.id, {
        accumulatedAmount: dto.accumulatedAmount ?? existing.accumulatedAmount,
        releasedAmount: dto.releasedAmount ?? existing.releasedAmount,
        releaseDate: dto.releaseDate ?? existing.releaseDate,
      });
      return updated!;
    }

    return this.create(ctx, {
      contratoId: dto.contratoId,
      accumulatedAmount: dto.accumulatedAmount ?? 0,
      releasedAmount: dto.releasedAmount ?? 0,
      releaseDate: dto.releaseDate ?? null,
    });
  }

  /**
   * Añade monto al fondo acumulado
   */
  async addAccumulation(
    ctx: ServiceContext,
    contratoId: string,
    amount: number
  ): Promise<FondoGarantia> {
    let fondo = await this.findByContrato(ctx, contratoId);

    if (!fondo) {
      fondo = await this.create(ctx, {
        contratoId,
        accumulatedAmount: amount,
        releasedAmount: 0,
      });
      return fondo;
    }

    const currentAccumulated = Number(fondo.accumulatedAmount) || 0;
    const updated = await this.update(ctx, fondo.id, {
      accumulatedAmount: currentAccumulated + amount,
    });

    return updated!;
  }

  /**
   * Libera parcialmente el fondo de garantía
   */
  async releasePartial(
    ctx: ServiceContext,
    contratoId: string,
    dto: ReleaseFondoDto
  ): Promise<FondoGarantia> {
    const fondo = await this.findByContrato(ctx, contratoId);

    if (!fondo) {
      throw new Error('Fondo de garantía no encontrado');
    }

    const accumulated = Number(fondo.accumulatedAmount) || 0;
    const released = Number(fondo.releasedAmount) || 0;
    const pending = accumulated - released;

    if (dto.amount > pending) {
      throw new Error(`Monto a liberar (${dto.amount}) excede el monto pendiente (${pending})`);
    }

    const updated = await this.update(ctx, fondo.id, {
      releasedAmount: released + dto.amount,
      releasedAt: dto.releaseDate || new Date(),
      releasedById: dto.releasedById,
    });

    return updated!;
  }

  /**
   * Libera completamente el fondo de garantía
   */
  async releaseFull(
    ctx: ServiceContext,
    contratoId: string,
    releasedById: string,
    releaseDate?: Date
  ): Promise<FondoGarantia> {
    const fondo = await this.findByContrato(ctx, contratoId);

    if (!fondo) {
      throw new Error('Fondo de garantía no encontrado');
    }

    const accumulated = Number(fondo.accumulatedAmount) || 0;
    const released = Number(fondo.releasedAmount) || 0;
    const pending = accumulated - released;

    if (pending <= 0) {
      throw new Error('No hay monto pendiente para liberar');
    }

    const updated = await this.update(ctx, fondo.id, {
      releasedAmount: accumulated,
      releasedAt: releaseDate || new Date(),
      releasedById,
    });

    return updated!;
  }

  /**
   * Obtiene estadísticas de fondos de garantía
   */
  async getStats(ctx: ServiceContext): Promise<FondoGarantiaStats> {
    const fondos = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        deletedAt: IsNull(),
      },
    });

    const stats: FondoGarantiaStats = {
      totalAccumulated: 0,
      totalReleased: 0,
      totalPending: 0,
      fondosCount: fondos.length,
      fondosPendingRelease: 0,
      fondosFullyReleased: 0,
    };

    fondos.forEach((fondo) => {
      const accumulated = Number(fondo.accumulatedAmount) || 0;
      const released = Number(fondo.releasedAmount) || 0;
      const pending = accumulated - released;

      stats.totalAccumulated += accumulated;
      stats.totalReleased += released;
      stats.totalPending += pending;

      if (pending > 0) {
        stats.fondosPendingRelease++;
      } else if (released > 0 && pending === 0) {
        stats.fondosFullyReleased++;
      }
    });

    return stats;
  }

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

    if (filters.contratoId) {
      qb.andWhere('f.contrato_id = :contratoId', { contratoId: filters.contratoId });
    }

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

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

    // Aplicar filtros post-query si es necesario
    let filteredData = data;

    if (filters.hasPending !== undefined) {
      filteredData = filteredData.filter((fondo) => {
        const pending = (Number(fondo.accumulatedAmount) || 0) - (Number(fondo.releasedAmount) || 0);
        return filters.hasPending ? pending > 0 : pending === 0;
      });
    }

    if (filters.fullyReleased !== undefined) {
      filteredData = filteredData.filter((fondo) => {
        const accumulated = Number(fondo.accumulatedAmount) || 0;
        const released = Number(fondo.releasedAmount) || 0;
        return filters.fullyReleased
          ? released > 0 && released >= accumulated
          : released < accumulated;
      });
    }

    const finalTotal = filteredData.length !== data.length ? filteredData.length : total;

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