import { Repository, FindOptionsWhere, ILike, Between, In } from 'typeorm';
import { AppDataSource } from '../../../config/typeorm.js';
import { GastoViaje, TipoGasto } from '../entities/gasto-viaje.entity.js';
import { EstadoGasto } from '../entities/carga-combustible.entity.js';

/**
 * Search parameters for travel expenses
 */
export interface GastoViajeSearchParams {
  tenantId: string;
  search?: string;
  viajeId?: string;
  operadorId?: string;
  tipoGasto?: TipoGasto;
  estado?: EstadoGasto;
  estados?: EstadoGasto[];
  tieneFactura?: boolean;
  fechaDesde?: Date;
  fechaHasta?: Date;
  limit?: number;
  offset?: number;
}

/**
 * DTO for creating a travel expense
 */
export interface CreateGastoViajeDto {
  viajeId: string;
  operadorId: string;
  tipoGasto: TipoGasto;
  descripcion: string;
  monto: number;
  tieneFactura?: boolean;
  numeroFactura?: string;
  numeroTicket?: string;
  fotoComprobanteUrl?: string;
  lugar?: string;
  latitud?: number;
  longitud?: number;
  fechaGasto: Date;
}

/**
 * DTO for updating a travel expense
 */
export interface UpdateGastoViajeDto extends Partial<CreateGastoViajeDto> {
  estado?: EstadoGasto;
  motivoRechazo?: string;
}

/**
 * Service for managing travel expenses (Gastos de Viaje)
 */
export class GastoViajeService {
  private repository: Repository<GastoViaje>;

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

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

    const where: FindOptionsWhere<GastoViaje>[] = [];
    const baseWhere: FindOptionsWhere<GastoViaje> = { tenantId };

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

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

    if (tipoGasto) {
      baseWhere.tipoGasto = tipoGasto;
    }

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

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

    if (tieneFactura !== undefined) {
      baseWhere.tieneFactura = tieneFactura;
    }

    if (fechaDesde && fechaHasta) {
      baseWhere.fechaGasto = Between(fechaDesde, fechaHasta);
    }

    if (search) {
      where.push(
        { ...baseWhere, descripcion: ILike(`%${search}%`) },
        { ...baseWhere, lugar: ILike(`%${search}%`) },
        { ...baseWhere, numeroFactura: ILike(`%${search}%`) }
      );
    } else {
      where.push(baseWhere);
    }

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

    return { data, total };
  }

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

  /**
   * Create a new travel expense
   */
  async create(tenantId: string, dto: CreateGastoViajeDto, createdBy: string): Promise<GastoViaje> {
    const gasto = this.repository.create({
      ...dto,
      tenantId,
      estado: EstadoGasto.PENDIENTE,
      createdById: createdBy,
    });

    return this.repository.save(gasto);
  }

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

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

  /**
   * Approve a travel expense
   */
  async aprobar(tenantId: string, id: string, aprobadoPor: string): Promise<GastoViaje | null> {
    const gasto = await this.findOne(tenantId, id);
    if (!gasto) return null;

    if (gasto.estado !== EstadoGasto.PENDIENTE) {
      throw new Error('Solo se pueden aprobar gastos pendientes');
    }

    gasto.estado = EstadoGasto.APROBADO;
    gasto.aprobadoPor = aprobadoPor;
    gasto.aprobadoFecha = new Date();

    return this.repository.save(gasto);
  }

  /**
   * Reject a travel expense
   */
  async rechazar(
    tenantId: string,
    id: string,
    aprobadoPor: string,
    motivoRechazo: string
  ): Promise<GastoViaje | null> {
    const gasto = await this.findOne(tenantId, id);
    if (!gasto) return null;

    if (gasto.estado !== EstadoGasto.PENDIENTE) {
      throw new Error('Solo se pueden rechazar gastos pendientes');
    }

    gasto.estado = EstadoGasto.RECHAZADO;
    gasto.aprobadoPor = aprobadoPor;
    gasto.aprobadoFecha = new Date();
    gasto.motivoRechazo = motivoRechazo;

    return this.repository.save(gasto);
  }

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

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

  /**
   * Get travel expenses by trip
   */
  async getGastosPorViaje(viajeId: string, tenantId: string): Promise<GastoViaje[]> {
    return this.repository.find({
      where: { viajeId, tenantId },
      order: { fechaGasto: 'ASC' },
    });
  }

  /**
   * Get travel expenses by operator
   */
  async getGastosPorOperador(operadorId: string, tenantId: string): Promise<GastoViaje[]> {
    return this.repository.find({
      where: { operadorId, tenantId },
      order: { fechaGasto: 'DESC' },
      take: 50,
    });
  }

  /**
   * Get pending travel expenses
   */
  async getGastosPendientes(tenantId: string): Promise<GastoViaje[]> {
    return this.repository.find({
      where: { tenantId, estado: EstadoGasto.PENDIENTE },
      order: { fechaGasto: 'DESC' },
    });
  }

  /**
   * Calculate total expenses for a trip
   */
  async getTotalGastosViaje(
    viajeId: string,
    tenantId: string
  ): Promise<{ total: number; porTipo: Record<string, number> }> {
    const gastos = await this.getGastosPorViaje(viajeId, tenantId);

    const porTipo: Record<string, number> = {};
    let total = 0;

    for (const gasto of gastos) {
      if (gasto.estado === EstadoGasto.APROBADO || gasto.estado === EstadoGasto.PAGADO) {
        total += Number(gasto.monto);
        porTipo[gasto.tipoGasto] = (porTipo[gasto.tipoGasto] || 0) + Number(gasto.monto);
      }
    }

    return { total, porTipo };
  }

  /**
   * Get expense statistics by type for a period
   */
  async getEstadisticasPorTipo(
    tenantId: string,
    fechaInicio: Date,
    fechaFin: Date
  ): Promise<Record<string, { cantidad: number; total: number }>> {
    const gastos = await this.repository.find({
      where: {
        tenantId,
        estado: In([EstadoGasto.APROBADO, EstadoGasto.PAGADO]),
        fechaGasto: Between(fechaInicio, fechaFin),
      },
    });

    const estadisticas: Record<string, { cantidad: number; total: number }> = {};

    for (const gasto of gastos) {
      if (!estadisticas[gasto.tipoGasto]) {
        estadisticas[gasto.tipoGasto] = { cantidad: 0, total: 0 };
      }
      estadisticas[gasto.tipoGasto].cantidad++;
      estadisticas[gasto.tipoGasto].total += Number(gasto.monto);
    }

    return estadisticas;
  }

  /**
   * Get deductible (with invoice) expenses
   */
  async getGastosDeducibles(
    tenantId: string,
    fechaInicio: Date,
    fechaFin: Date
  ): Promise<GastoViaje[]> {
    return this.repository.find({
      where: {
        tenantId,
        tieneFactura: true,
        estado: In([EstadoGasto.APROBADO, EstadoGasto.PAGADO]),
        fechaGasto: Between(fechaInicio, fechaFin),
      },
      order: { fechaGasto: 'DESC' },
    });
  }
}

export const gastoViajeService = new GastoViajeService();
