/**
 * ResiduoPeligrosoService - Servicio para gestión de residuos peligrosos
 *
 * RF-MAA017-006: Gestión especializada de residuos peligrosos con seguimiento
 * completo desde generación hasta disposición final.
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { ResiduoCatalogo, CategoriaResiduo } from '../entities/residuo-catalogo.entity';
import { ResiduoGeneracion, UnidadResiduo, EstadoResiduo } from '../entities/residuo-generacion.entity';
import { AlmacenTemporal } from '../entities/almacen-temporal.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface ResiduoPeligrosoFilters {
  fraccionamientoId?: string;
  residuoId?: string;
  estado?: EstadoResiduo;
  categoria?: CategoriaResiduo;
  areaGeneracion?: string;
  contenedorId?: string;
  dateFrom?: Date;
  dateTo?: Date;
  vencidos?: boolean;
}

export interface CreateResiduoPeligrosoDto {
  fraccionamientoId: string;
  residuoId: string;
  fechaGeneracion: Date;
  cantidad: number;
  unidad: UnidadResiduo;
  areaGeneracion?: string;
  fuente?: string;
  contenedorId?: string;
  fotoUrl?: string;
  ubicacionGeo?: { lat: number; lng: number };
}

export interface UpdateResiduoPeligrosoDto {
  cantidad?: number;
  unidad?: UnidadResiduo;
  areaGeneracion?: string;
  fuente?: string;
  contenedorId?: string;
  fotoUrl?: string;
}

export interface TransferirResiduoDto {
  contenedorDestinoId: string;
  cantidad: number;
  observaciones?: string;
}

export interface ResiduoPeligrosoStats {
  totalGenerado: number;
  totalAlmacenado: number;
  totalDispuesto: number;
  residuosVencidos: number;
  proximosVencer: number;
  porCategoria: { categoria: string; cantidad: number; unidad: string }[];
  porArea: { area: string; cantidad: number }[];
  tendenciaMensual: { mes: string; cantidad: number }[];
}

export interface AlertaVencimiento {
  residuoGeneracionId: string;
  residuoNombre: string;
  fechaGeneracion: Date;
  diasAlmacenado: number;
  tiempoMaximo: number;
  diasRestantes: number;
  estado: 'normal' | 'proximo' | 'vencido';
}

export class ResiduoPeligrosoService {
  constructor(
    private readonly residuoCatalogoRepository: Repository<ResiduoCatalogo>,
    private readonly generacionRepository: Repository<ResiduoGeneracion>,
    private readonly almacenRepository: Repository<AlmacenTemporal>
  ) {}

  // ========== Catálogo de Residuos Peligrosos ==========
  async findResiduosPeligrosos(
    search?: string,
    activo: boolean = true
  ): Promise<ResiduoCatalogo[]> {
    const queryBuilder = this.residuoCatalogoRepository
      .createQueryBuilder('r')
      .where('r.categoria = :categoria', { categoria: 'peligroso' })
      .andWhere('r.activo = :activo', { activo });

    if (search) {
      queryBuilder.andWhere(
        '(r.codigo ILIKE :search OR r.nombre ILIKE :search)',
        { search: `%${search}%` }
      );
    }

    return queryBuilder.orderBy('r.nombre', 'ASC').getMany();
  }

  async findResiduoById(id: string): Promise<ResiduoCatalogo | null> {
    return this.residuoCatalogoRepository.findOne({
      where: { id, categoria: 'peligroso' } as FindOptionsWhere<ResiduoCatalogo>,
    });
  }

  async findResiduoByCodigo(codigo: string): Promise<ResiduoCatalogo | null> {
    return this.residuoCatalogoRepository.findOne({
      where: { codigo } as FindOptionsWhere<ResiduoCatalogo>,
    });
  }

  // ========== Generación de Residuos Peligrosos ==========
  async findGeneraciones(
    ctx: ServiceContext,
    filters: ResiduoPeligrosoFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ResiduoGeneracion>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.generacionRepository
      .createQueryBuilder('g')
      .innerJoinAndSelect('g.residuo', 'residuo')
      .leftJoinAndSelect('g.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('g.createdBy', 'createdBy')
      .where('g.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('residuo.categoria = :categoria', { categoria: 'peligroso' });

    if (filters.fraccionamientoId) {
      queryBuilder.andWhere('g.fraccionamiento_id = :fraccionamientoId', {
        fraccionamientoId: filters.fraccionamientoId,
      });
    }

    if (filters.residuoId) {
      queryBuilder.andWhere('g.residuo_id = :residuoId', {
        residuoId: filters.residuoId,
      });
    }

    if (filters.estado) {
      queryBuilder.andWhere('g.estado = :estado', { estado: filters.estado });
    }

    if (filters.areaGeneracion) {
      queryBuilder.andWhere('g.area_generacion ILIKE :area', {
        area: `%${filters.areaGeneracion}%`,
      });
    }

    if (filters.contenedorId) {
      queryBuilder.andWhere('g.contenedor_id = :contenedorId', {
        contenedorId: filters.contenedorId,
      });
    }

    if (filters.dateFrom) {
      queryBuilder.andWhere('g.fecha_generacion >= :dateFrom', {
        dateFrom: filters.dateFrom,
      });
    }

    if (filters.dateTo) {
      queryBuilder.andWhere('g.fecha_generacion <= :dateTo', {
        dateTo: filters.dateTo,
      });
    }

    if (filters.vencidos) {
      // Residuos que han excedido su tiempo máximo de almacenamiento
      queryBuilder.andWhere('g.estado = :estadoAlmacenado', { estadoAlmacenado: 'almacenado' });
      queryBuilder.andWhere(
        "g.fecha_generacion + (residuo.tiempo_max_almacen_dias || ' days')::interval < NOW()"
      );
    }

    queryBuilder
      .orderBy('g.fecha_generacion', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  async findGeneracionById(ctx: ServiceContext, id: string): Promise<ResiduoGeneracion | null> {
    return this.generacionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ResiduoGeneracion>,
      relations: ['residuo', 'fraccionamiento', 'createdBy'],
    });
  }

  async createGeneracion(ctx: ServiceContext, dto: CreateResiduoPeligrosoDto): Promise<ResiduoGeneracion> {
    // Validate that the residuo is peligroso
    const residuo = await this.residuoCatalogoRepository.findOne({
      where: { id: dto.residuoId } as FindOptionsWhere<ResiduoCatalogo>,
    });

    if (!residuo) {
      throw new Error('Residuo no encontrado en el catálogo');
    }

    if (residuo.categoria !== 'peligroso') {
      throw new Error('Este servicio solo gestiona residuos peligrosos');
    }

    const generacion = this.generacionRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      residuoId: dto.residuoId,
      fechaGeneracion: dto.fechaGeneracion,
      cantidad: dto.cantidad,
      unidad: dto.unidad,
      areaGeneracion: dto.areaGeneracion,
      fuente: dto.fuente,
      contenedorId: dto.contenedorId,
      fotoUrl: dto.fotoUrl,
      estado: 'almacenado',
      createdById: ctx.userId,
    });

    if (dto.ubicacionGeo) {
      generacion.ubicacionGeo = `POINT(${dto.ubicacionGeo.lng} ${dto.ubicacionGeo.lat})`;
    }

    return this.generacionRepository.save(generacion);
  }

  async updateGeneracion(
    ctx: ServiceContext,
    id: string,
    dto: UpdateResiduoPeligrosoDto
  ): Promise<ResiduoGeneracion | null> {
    const generacion = await this.generacionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ResiduoGeneracion>,
    });

    if (!generacion) return null;

    if (generacion.estado !== 'almacenado') {
      throw new Error('Solo se pueden modificar residuos en estado almacenado');
    }

    if (dto.cantidad !== undefined) generacion.cantidad = dto.cantidad;
    if (dto.unidad !== undefined) generacion.unidad = dto.unidad;
    if (dto.areaGeneracion !== undefined) generacion.areaGeneracion = dto.areaGeneracion;
    if (dto.fuente !== undefined) generacion.fuente = dto.fuente;
    if (dto.contenedorId !== undefined) generacion.contenedorId = dto.contenedorId;
    if (dto.fotoUrl !== undefined) generacion.fotoUrl = dto.fotoUrl;

    return this.generacionRepository.save(generacion);
  }

  async updateEstado(
    ctx: ServiceContext,
    id: string,
    estado: EstadoResiduo
  ): Promise<ResiduoGeneracion | null> {
    const generacion = await this.generacionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ResiduoGeneracion>,
    });

    if (!generacion) return null;

    generacion.estado = estado;
    return this.generacionRepository.save(generacion);
  }

  async transferirContenedor(
    ctx: ServiceContext,
    id: string,
    dto: TransferirResiduoDto
  ): Promise<ResiduoGeneracion | null> {
    const generacion = await this.generacionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ResiduoGeneracion>,
    });

    if (!generacion) return null;

    if (generacion.estado !== 'almacenado') {
      throw new Error('Solo se pueden transferir residuos en estado almacenado');
    }

    // Verify destination container exists
    const contenedorDestino = await this.almacenRepository.findOne({
      where: { id: dto.contenedorDestinoId, tenantId: ctx.tenantId } as FindOptionsWhere<AlmacenTemporal>,
    });

    if (!contenedorDestino) {
      throw new Error('Contenedor destino no encontrado');
    }

    if (contenedorDestino.estado === 'lleno') {
      throw new Error('El contenedor destino está lleno');
    }

    generacion.contenedorId = dto.contenedorDestinoId;
    return this.generacionRepository.save(generacion);
  }

  // ========== Alertas de Vencimiento ==========
  async getAlertasVencimiento(
    ctx: ServiceContext,
    fraccionamientoId?: string,
    diasProximoVencer: number = 7
  ): Promise<AlertaVencimiento[]> {
    const queryBuilder = this.generacionRepository
      .createQueryBuilder('g')
      .innerJoinAndSelect('g.residuo', 'r')
      .where('g.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('g.estado = :estado', { estado: 'almacenado' })
      .andWhere('r.categoria = :categoria', { categoria: 'peligroso' })
      .andWhere('r.tiempo_max_almacen_dias IS NOT NULL');

    if (fraccionamientoId) {
      queryBuilder.andWhere('g.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }

    const generaciones = await queryBuilder.getMany();

    const alertas: AlertaVencimiento[] = [];
    const today = new Date();

    for (const gen of generaciones) {
      const fechaGen = new Date(gen.fechaGeneracion);
      const diasAlmacenado = Math.floor((today.getTime() - fechaGen.getTime()) / (1000 * 60 * 60 * 24));
      const tiempoMax = gen.residuo.tiempoMaxAlmacenDias || 180; // Default 180 days
      const diasRestantes = tiempoMax - diasAlmacenado;

      let estado: 'normal' | 'proximo' | 'vencido' = 'normal';
      if (diasRestantes <= 0) {
        estado = 'vencido';
      } else if (diasRestantes <= diasProximoVencer) {
        estado = 'proximo';
      }

      if (estado !== 'normal') {
        alertas.push({
          residuoGeneracionId: gen.id,
          residuoNombre: gen.residuo.nombre,
          fechaGeneracion: gen.fechaGeneracion,
          diasAlmacenado,
          tiempoMaximo: tiempoMax,
          diasRestantes: Math.max(0, diasRestantes),
          estado,
        });
      }
    }

    // Sort by dias restantes (most urgent first)
    alertas.sort((a, b) => a.diasRestantes - b.diasRestantes);

    return alertas;
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<ResiduoPeligrosoStats> {
    const baseQuery = () => {
      const qb = this.generacionRepository
        .createQueryBuilder('g')
        .innerJoin('g.residuo', 'r')
        .where('g.tenant_id = :tenantId', { tenantId: ctx.tenantId })
        .andWhere('r.categoria = :categoria', { categoria: 'peligroso' });

      if (fraccionamientoId) {
        qb.andWhere('g.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
      }
      return qb;
    };

    // Total generado
    const totalGeneradoResult = await baseQuery()
      .select('SUM(g.cantidad)', 'total')
      .getRawOne();
    const totalGenerado = parseFloat(totalGeneradoResult?.total || '0');

    // Total almacenado
    const totalAlmacenadoResult = await baseQuery()
      .andWhere('g.estado = :estado', { estado: 'almacenado' })
      .select('SUM(g.cantidad)', 'total')
      .getRawOne();
    const totalAlmacenado = parseFloat(totalAlmacenadoResult?.total || '0');

    // Total dispuesto
    const totalDispuestoResult = await baseQuery()
      .andWhere('g.estado = :estado', { estado: 'dispuesto' })
      .select('SUM(g.cantidad)', 'total')
      .getRawOne();
    const totalDispuesto = parseFloat(totalDispuestoResult?.total || '0');

    // Residuos vencidos
    const vencidosResult = await baseQuery()
      .andWhere('g.estado = :estado', { estado: 'almacenado' })
      .andWhere("g.fecha_generacion + (r.tiempo_max_almacen_dias || ' days')::interval < NOW()")
      .getCount();

    // Próximos a vencer (7 días)
    const proximosResult = await baseQuery()
      .andWhere('g.estado = :estado', { estado: 'almacenado' })
      .andWhere("g.fecha_generacion + (r.tiempo_max_almacen_dias || ' days')::interval >= NOW()")
      .andWhere("g.fecha_generacion + ((r.tiempo_max_almacen_dias - 7) || ' days')::interval < NOW()")
      .getCount();

    // Por categoría (subcategoría CRETIB)
    const porCategoriaRaw = await baseQuery()
      .select('r.caracteristicas_cretib', 'categoria')
      .addSelect('g.unidad', 'unidad')
      .addSelect('SUM(g.cantidad)', 'cantidad')
      .groupBy('r.caracteristicas_cretib')
      .addGroupBy('g.unidad')
      .getRawMany();

    const porCategoria = porCategoriaRaw.map((c) => ({
      categoria: c.categoria || 'Sin clasificar',
      cantidad: parseFloat(c.cantidad || '0'),
      unidad: c.unidad,
    }));

    // Por área de generación
    const porAreaRaw = await baseQuery()
      .select('g.area_generacion', 'area')
      .addSelect('SUM(g.cantidad)', 'cantidad')
      .groupBy('g.area_generacion')
      .getRawMany();

    const porArea = porAreaRaw
      .filter((a) => a.area)
      .map((a) => ({
        area: a.area,
        cantidad: parseFloat(a.cantidad || '0'),
      }));

    // Tendencia mensual (últimos 6 meses)
    const sixMonthsAgo = new Date();
    sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);

    const tendenciaRaw = await baseQuery()
      .andWhere('g.fecha_generacion >= :sixMonthsAgo', { sixMonthsAgo })
      .select("TO_CHAR(g.fecha_generacion, 'YYYY-MM')", 'mes')
      .addSelect('SUM(g.cantidad)', 'cantidad')
      .groupBy("TO_CHAR(g.fecha_generacion, 'YYYY-MM')")
      .orderBy("TO_CHAR(g.fecha_generacion, 'YYYY-MM')", 'ASC')
      .getRawMany();

    const tendenciaMensual = tendenciaRaw.map((t) => ({
      mes: t.mes,
      cantidad: parseFloat(t.cantidad || '0'),
    }));

    return {
      totalGenerado,
      totalAlmacenado,
      totalDispuesto,
      residuosVencidos: vencidosResult,
      proximosVencer: proximosResult,
      porCategoria,
      porArea,
      tendenciaMensual,
    };
  }

  // ========== Trazabilidad ==========
  async getHistorialResiduo(ctx: ServiceContext, residuoId: string): Promise<ResiduoGeneracion[]> {
    return this.generacionRepository.find({
      where: {
        tenantId: ctx.tenantId,
        residuoId,
      } as FindOptionsWhere<ResiduoGeneracion>,
      relations: ['residuo', 'fraccionamiento', 'createdBy'],
      order: { fechaGeneracion: 'DESC' },
    });
  }

  async getGeneracionesPorContenedor(
    ctx: ServiceContext,
    contenedorId: string
  ): Promise<ResiduoGeneracion[]> {
    return this.generacionRepository.find({
      where: {
        tenantId: ctx.tenantId,
        contenedorId,
        estado: 'almacenado',
      } as FindOptionsWhere<ResiduoGeneracion>,
      relations: ['residuo'],
      order: { fechaGeneracion: 'ASC' },
    });
  }
}
