import { Repository, FindOptionsWhere, Between, In } from 'typeorm';
import { AppDataSource } from '../../../config/typeorm.js';
import { AnticipoViatico, EstadoAnticipo } from '../entities/anticipo-viatico.entity.js';

/**
 * Search parameters for travel advances
 */
export interface AnticipoViaticoSearchParams {
  tenantId: string;
  viajeId?: string;
  operadorId?: string;
  estado?: string;
  estados?: string[];
  fechaDesde?: Date;
  fechaHasta?: Date;
  limit?: number;
  offset?: number;
}

/**
 * DTO for creating a travel advance
 */
export interface CreateAnticipoViaticoDto {
  viajeId: string;
  operadorId: string;
  montoSolicitado: number;
  combustibleEstimado?: number;
  peajesEstimado?: number;
  viaticosEstimado?: number;
  observaciones?: string;
}

/**
 * DTO for updating a travel advance
 */
export interface UpdateAnticipoViaticoDto extends Partial<CreateAnticipoViaticoDto> {
  estado?: string;
  montoAprobado?: number;
  montoComprobado?: number;
  montoReintegro?: number;
}

/**
 * Service for managing travel advances (Anticipos de Viaticos)
 */
export class AnticipoViaticoService {
  private repository: Repository<AnticipoViatico>;

  constructor() {
    this.repository = AppDataSource.getRepository(AnticipoViatico);
  }

  /**
   * Find all travel advances with filters
   */
  async findAll(params: AnticipoViaticoSearchParams): Promise<{ data: AnticipoViatico[]; total: number }> {
    const {
      tenantId,
      viajeId,
      operadorId,
      estado,
      estados,
      fechaDesde,
      fechaHasta,
      limit = 50,
      offset = 0,
    } = params;

    const where: FindOptionsWhere<AnticipoViatico> = { tenantId };

    if (viajeId) {
      where.viajeId = viajeId;
    }

    if (operadorId) {
      where.operadorId = operadorId;
    }

    if (estado) {
      where.estado = estado;
    }

    if (estados && estados.length > 0) {
      where.estado = In(estados);
    }

    if (fechaDesde && fechaHasta) {
      where.fechaSolicitud = Between(fechaDesde, fechaHasta);
    }

    const [data, total] = await this.repository.findAndCount({
      where,
      take: limit,
      skip: offset,
      order: { fechaSolicitud: 'DESC' },
    });

    return { data, total };
  }

  /**
   * Find a single travel advance by ID
   */
  async findOne(tenantId: string, id: string): Promise<AnticipoViatico | null> {
    return this.repository.findOne({
      where: { id, tenantId },
    });
  }

  /**
   * Find travel advance by trip
   */
  async findByViaje(viajeId: string, tenantId: string): Promise<AnticipoViatico | null> {
    return this.repository.findOne({
      where: { viajeId, tenantId },
    });
  }

  /**
   * Create a new travel advance
   */
  async create(tenantId: string, dto: CreateAnticipoViaticoDto, createdBy: string): Promise<AnticipoViatico> {
    // Check if advance already exists for trip
    const existing = await this.findByViaje(dto.viajeId, tenantId);
    if (existing) {
      throw new Error('Ya existe un anticipo para este viaje');
    }

    const anticipo = this.repository.create({
      ...dto,
      tenantId,
      estado: EstadoAnticipo.SOLICITADO,
      fechaSolicitud: new Date(),
      createdById: createdBy,
    });

    return this.repository.save(anticipo);
  }

  /**
   * Update an existing travel advance
   */
  async update(
    tenantId: string,
    id: string,
    dto: UpdateAnticipoViaticoDto
  ): Promise<AnticipoViatico | null> {
    const anticipo = await this.findOne(tenantId, id);
    if (!anticipo) return null;

    Object.assign(anticipo, dto);
    return this.repository.save(anticipo);
  }

  /**
   * Approve a travel advance
   */
  async aprobar(
    tenantId: string,
    id: string,
    montoAprobado: number,
    aprobadoPor: string
  ): Promise<AnticipoViatico | null> {
    const anticipo = await this.findOne(tenantId, id);
    if (!anticipo) return null;

    if (anticipo.estado !== EstadoAnticipo.SOLICITADO) {
      throw new Error('Solo se pueden aprobar anticipos solicitados');
    }

    anticipo.estado = EstadoAnticipo.APROBADO;
    anticipo.montoAprobado = montoAprobado;
    anticipo.aprobadoPor = aprobadoPor;
    anticipo.fechaAprobacion = new Date();

    return this.repository.save(anticipo);
  }

  /**
   * Reject a travel advance
   */
  async rechazar(
    tenantId: string,
    id: string,
    aprobadoPor: string,
    observaciones?: string
  ): Promise<AnticipoViatico | null> {
    const anticipo = await this.findOne(tenantId, id);
    if (!anticipo) return null;

    if (anticipo.estado !== EstadoAnticipo.SOLICITADO) {
      throw new Error('Solo se pueden rechazar anticipos solicitados');
    }

    anticipo.estado = EstadoAnticipo.RECHAZADO;
    anticipo.aprobadoPor = aprobadoPor;
    anticipo.fechaAprobacion = new Date();
    if (observaciones) {
      anticipo.observaciones = observaciones;
    }

    return this.repository.save(anticipo);
  }

  /**
   * Mark advance as delivered
   */
  async entregar(
    tenantId: string,
    id: string,
    entregadoPor: string
  ): Promise<AnticipoViatico | null> {
    const anticipo = await this.findOne(tenantId, id);
    if (!anticipo) return null;

    if (anticipo.estado !== EstadoAnticipo.APROBADO) {
      throw new Error('Solo se pueden entregar anticipos aprobados');
    }

    anticipo.estado = EstadoAnticipo.ENTREGADO;
    anticipo.entregadoPor = entregadoPor;
    anticipo.fechaEntrega = new Date();

    return this.repository.save(anticipo);
  }

  /**
   * Start accounting process
   */
  async iniciarComprobacion(tenantId: string, id: string): Promise<AnticipoViatico | null> {
    const anticipo = await this.findOne(tenantId, id);
    if (!anticipo) return null;

    if (anticipo.estado !== EstadoAnticipo.ENTREGADO) {
      throw new Error('Solo se pueden comprobar anticipos entregados');
    }

    anticipo.estado = EstadoAnticipo.COMPROBANDO;

    return this.repository.save(anticipo);
  }

  /**
   * Update accounted amount
   */
  async actualizarComprobado(
    tenantId: string,
    id: string,
    montoComprobado: number
  ): Promise<AnticipoViatico | null> {
    const anticipo = await this.findOne(tenantId, id);
    if (!anticipo) return null;

    if (anticipo.estado !== EstadoAnticipo.COMPROBANDO) {
      throw new Error('Solo se pueden actualizar anticipos en comprobacion');
    }

    anticipo.montoComprobado = montoComprobado;

    // Calculate reintegration if over
    const aprobado = anticipo.montoAprobado || 0;
    if (montoComprobado < aprobado) {
      anticipo.montoReintegro = aprobado - montoComprobado;
    }

    return this.repository.save(anticipo);
  }

  /**
   * Liquidate the advance
   */
  async liquidar(
    tenantId: string,
    id: string,
    montoComprobadoFinal: number,
    liquidadoPor: string
  ): Promise<AnticipoViatico | null> {
    const anticipo = await this.findOne(tenantId, id);
    if (!anticipo) return null;

    if (anticipo.estado !== EstadoAnticipo.COMPROBANDO && anticipo.estado !== EstadoAnticipo.ENTREGADO) {
      throw new Error('Solo se pueden liquidar anticipos entregados o en comprobacion');
    }

    anticipo.estado = EstadoAnticipo.LIQUIDADO;
    anticipo.montoComprobado = montoComprobadoFinal;
    anticipo.liquidadoPor = liquidadoPor;
    anticipo.fechaLiquidacion = new Date();

    // Calculate reintegration
    const aprobado = anticipo.montoAprobado || 0;
    anticipo.montoReintegro = Math.max(0, aprobado - montoComprobadoFinal);

    return this.repository.save(anticipo);
  }

  /**
   * Delete a travel advance (only if not delivered)
   */
  async delete(tenantId: string, id: string): Promise<boolean> {
    const anticipo = await this.findOne(tenantId, id);
    if (!anticipo) return false;

    if (anticipo.estado !== EstadoAnticipo.SOLICITADO && anticipo.estado !== EstadoAnticipo.RECHAZADO) {
      throw new Error('Solo se pueden eliminar anticipos solicitados o rechazados');
    }

    const result = await this.repository.delete(id);
    return (result.affected ?? 0) > 0;
  }

  /**
   * Get advances by operator
   */
  async getAnticiposPorOperador(operadorId: string, tenantId: string): Promise<AnticipoViatico[]> {
    return this.repository.find({
      where: { operadorId, tenantId },
      order: { fechaSolicitud: 'DESC' },
      take: 20,
    });
  }

  /**
   * Get pending advances (not liquidated)
   */
  async getAnticiposPendientes(tenantId: string): Promise<AnticipoViatico[]> {
    return this.repository.find({
      where: {
        tenantId,
        estado: In([
          EstadoAnticipo.SOLICITADO,
          EstadoAnticipo.APROBADO,
          EstadoAnticipo.ENTREGADO,
          EstadoAnticipo.COMPROBANDO,
        ]),
      },
      order: { fechaSolicitud: 'DESC' },
    });
  }

  /**
   * Get advances requiring reintegration
   */
  async getAnticiposConReintegro(tenantId: string): Promise<AnticipoViatico[]> {
    const qb = this.repository.createQueryBuilder('a');
    qb.where('a.tenant_id = :tenantId', { tenantId });
    qb.andWhere('a.estado = :estado', { estado: EstadoAnticipo.LIQUIDADO });
    qb.andWhere('a.monto_reintegro > 0');
    qb.orderBy('a.fecha_liquidacion', 'DESC');

    return qb.getMany();
  }

  /**
   * Get statistics for a period
   */
  async getEstadisticas(
    tenantId: string,
    fechaInicio: Date,
    fechaFin: Date
  ): Promise<{
    totalSolicitado: number;
    totalAprobado: number;
    totalComprobado: number;
    totalReintegrado: number;
    cantidadAnticipos: number;
  }> {
    const anticipos = await this.repository.find({
      where: {
        tenantId,
        estado: EstadoAnticipo.LIQUIDADO,
        fechaLiquidacion: Between(fechaInicio, fechaFin),
      },
    });

    return {
      totalSolicitado: anticipos.reduce((sum, a) => sum + Number(a.montoSolicitado), 0),
      totalAprobado: anticipos.reduce((sum, a) => sum + (Number(a.montoAprobado) || 0), 0),
      totalComprobado: anticipos.reduce((sum, a) => sum + Number(a.montoComprobado), 0),
      totalReintegrado: anticipos.reduce((sum, a) => sum + Number(a.montoReintegro), 0),
      cantidadAnticipos: anticipos.length,
    };
  }
}

export const anticipoViaticoService = new AnticipoViaticoService();
