/**
 * ProveedorAmbientalService - Servicio para gestión de proveedores ambientales
 *
 * RF-MAA017-006: Gestión de proveedores autorizados para transporte,
 * reciclaje y confinamiento de residuos.
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { ProveedorAmbiental, TipoProveedorAmbiental } from '../entities/proveedor-ambiental.entity';
import { ManifiestoResiduos } from '../entities/manifiesto-residuos.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface ProveedorFilters {
  tipo?: TipoProveedorAmbiental;
  activo?: boolean;
  vigente?: boolean;
  search?: string;
}

export interface CreateProveedorDto {
  razonSocial: string;
  rfc?: string;
  tipo: TipoProveedorAmbiental;
  numeroAutorizacion?: string;
  entidadAutorizadora?: string;
  fechaAutorizacion?: Date;
  fechaVencimiento?: Date;
  servicios?: string;
  contactoNombre?: string;
  contactoTelefono?: string;
}

export interface UpdateProveedorDto {
  razonSocial?: string;
  rfc?: string;
  tipo?: TipoProveedorAmbiental;
  numeroAutorizacion?: string;
  entidadAutorizadora?: string;
  fechaAutorizacion?: Date;
  fechaVencimiento?: Date;
  servicios?: string;
  contactoNombre?: string;
  contactoTelefono?: string;
  activo?: boolean;
}

export interface ProveedorConHistorial extends ProveedorAmbiental {
  totalManifiestos: number;
  ultimoServicio: Date | null;
  cantidadTotalKg: number;
}

export interface ProveedorStats {
  totalProveedores: number;
  proveedoresActivos: number;
  proveedoresVencidos: number;
  proximosVencer: number;
  porTipo: { tipo: string; count: number }[];
  topProveedores: { id: string; nombre: string; manifiestos: number }[];
}

export interface AlertaProveedor {
  proveedorId: string;
  razonSocial: string;
  tipo: 'vencimiento' | 'inactivo' | 'sin_autorizacion';
  mensaje: string;
  diasRestantes?: number;
  nivel: 'advertencia' | 'critico';
}

export class ProveedorAmbientalService {
  constructor(
    private readonly proveedorRepository: Repository<ProveedorAmbiental>,
    private readonly manifiestoRepository: Repository<ManifiestoResiduos>
  ) {}

  // ========== CRUD de Proveedores ==========
  async findAll(
    ctx: ServiceContext,
    filters: ProveedorFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ProveedorAmbiental>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.proveedorRepository
      .createQueryBuilder('p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.tipo) {
      queryBuilder.andWhere('p.tipo = :tipo', { tipo: filters.tipo });
    }

    if (filters.activo !== undefined) {
      queryBuilder.andWhere('p.activo = :activo', { activo: filters.activo });
    }

    if (filters.vigente === true) {
      const today = new Date();
      queryBuilder.andWhere(
        '(p.fecha_vencimiento IS NULL OR p.fecha_vencimiento >= :today)',
        { today }
      );
    } else if (filters.vigente === false) {
      const today = new Date();
      queryBuilder.andWhere('p.fecha_vencimiento < :today', { today });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(p.razon_social ILIKE :search OR p.rfc ILIKE :search OR p.numero_autorizacion ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    queryBuilder
      .orderBy('p.razon_social', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  async findById(ctx: ServiceContext, id: string): Promise<ProveedorAmbiental | null> {
    return this.proveedorRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ProveedorAmbiental>,
    });
  }

  async findByTipo(ctx: ServiceContext, tipo: TipoProveedorAmbiental): Promise<ProveedorAmbiental[]> {
    return this.proveedorRepository.find({
      where: {
        tenantId: ctx.tenantId,
        tipo,
        activo: true,
      } as FindOptionsWhere<ProveedorAmbiental>,
      order: { razonSocial: 'ASC' },
    });
  }

  async findTransportistas(ctx: ServiceContext): Promise<ProveedorAmbiental[]> {
    return this.findByTipo(ctx, 'transportista');
  }

  async findRecicladores(ctx: ServiceContext): Promise<ProveedorAmbiental[]> {
    return this.findByTipo(ctx, 'reciclador');
  }

  async findConfinamientos(ctx: ServiceContext): Promise<ProveedorAmbiental[]> {
    return this.findByTipo(ctx, 'confinamiento');
  }

  async findVigentes(ctx: ServiceContext, tipo?: TipoProveedorAmbiental): Promise<ProveedorAmbiental[]> {
    const today = new Date();
    const queryBuilder = this.proveedorRepository
      .createQueryBuilder('p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.activo = true')
      .andWhere('(p.fecha_vencimiento IS NULL OR p.fecha_vencimiento >= :today)', { today });

    if (tipo) {
      queryBuilder.andWhere('p.tipo = :tipo', { tipo });
    }

    return queryBuilder.orderBy('p.razon_social', 'ASC').getMany();
  }

  async create(ctx: ServiceContext, dto: CreateProveedorDto): Promise<ProveedorAmbiental> {
    // Validate RFC uniqueness within tenant
    if (dto.rfc) {
      const existing = await this.proveedorRepository.findOne({
        where: { tenantId: ctx.tenantId, rfc: dto.rfc } as FindOptionsWhere<ProveedorAmbiental>,
      });
      if (existing) {
        throw new Error('Ya existe un proveedor con este RFC');
      }
    }

    const proveedor = this.proveedorRepository.create({
      tenantId: ctx.tenantId,
      razonSocial: dto.razonSocial,
      rfc: dto.rfc,
      tipo: dto.tipo,
      numeroAutorizacion: dto.numeroAutorizacion,
      entidadAutorizadora: dto.entidadAutorizadora,
      fechaAutorizacion: dto.fechaAutorizacion,
      fechaVencimiento: dto.fechaVencimiento,
      servicios: dto.servicios,
      contactoNombre: dto.contactoNombre,
      contactoTelefono: dto.contactoTelefono,
      activo: true,
    });

    return this.proveedorRepository.save(proveedor);
  }

  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdateProveedorDto
  ): Promise<ProveedorAmbiental | null> {
    const proveedor = await this.findById(ctx, id);
    if (!proveedor) return null;

    // Validate RFC uniqueness if changing
    if (dto.rfc && dto.rfc !== proveedor.rfc) {
      const existing = await this.proveedorRepository.findOne({
        where: { tenantId: ctx.tenantId, rfc: dto.rfc } as FindOptionsWhere<ProveedorAmbiental>,
      });
      if (existing && existing.id !== id) {
        throw new Error('Ya existe un proveedor con este RFC');
      }
    }

    if (dto.razonSocial !== undefined) proveedor.razonSocial = dto.razonSocial;
    if (dto.rfc !== undefined) proveedor.rfc = dto.rfc;
    if (dto.tipo !== undefined) proveedor.tipo = dto.tipo;
    if (dto.numeroAutorizacion !== undefined) proveedor.numeroAutorizacion = dto.numeroAutorizacion;
    if (dto.entidadAutorizadora !== undefined) proveedor.entidadAutorizadora = dto.entidadAutorizadora;
    if (dto.fechaAutorizacion !== undefined) proveedor.fechaAutorizacion = dto.fechaAutorizacion;
    if (dto.fechaVencimiento !== undefined) proveedor.fechaVencimiento = dto.fechaVencimiento;
    if (dto.servicios !== undefined) proveedor.servicios = dto.servicios;
    if (dto.contactoNombre !== undefined) proveedor.contactoNombre = dto.contactoNombre;
    if (dto.contactoTelefono !== undefined) proveedor.contactoTelefono = dto.contactoTelefono;
    if (dto.activo !== undefined) proveedor.activo = dto.activo;

    return this.proveedorRepository.save(proveedor);
  }

  async activate(ctx: ServiceContext, id: string): Promise<ProveedorAmbiental | null> {
    return this.update(ctx, id, { activo: true });
  }

  async deactivate(ctx: ServiceContext, id: string): Promise<ProveedorAmbiental | null> {
    return this.update(ctx, id, { activo: false });
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    // Check if proveedor has manifiestos
    const manifiestoCount = await this.manifiestoRepository.count({
      where: [
        { tenantId: ctx.tenantId, transportistaId: id },
        { tenantId: ctx.tenantId, destinoId: id },
      ] as FindOptionsWhere<ManifiestoResiduos>[],
    });

    if (manifiestoCount > 0) {
      throw new Error('No se puede eliminar un proveedor con manifiestos asociados. Desactívelo en su lugar.');
    }

    const result = await this.proveedorRepository.delete({
      id,
      tenantId: ctx.tenantId,
    } as FindOptionsWhere<ProveedorAmbiental>);

    return (result.affected ?? 0) > 0;
  }

  // ========== Renovación de Autorización ==========
  async renovarAutorizacion(
    ctx: ServiceContext,
    id: string,
    nuevaFechaVencimiento: Date,
    nuevoNumeroAutorizacion?: string
  ): Promise<ProveedorAmbiental | null> {
    const proveedor = await this.findById(ctx, id);
    if (!proveedor) return null;

    proveedor.fechaVencimiento = nuevaFechaVencimiento;
    if (nuevoNumeroAutorizacion) {
      proveedor.numeroAutorizacion = nuevoNumeroAutorizacion;
    }
    proveedor.fechaAutorizacion = new Date();

    return this.proveedorRepository.save(proveedor);
  }

  // ========== Historial de Servicios ==========
  async getProveedorConHistorial(
    ctx: ServiceContext,
    id: string
  ): Promise<ProveedorConHistorial | null> {
    const proveedor = await this.findById(ctx, id);
    if (!proveedor) return null;

    // Count manifiestos where this proveedor is transportista or destino
    const totalManifiestos = await this.manifiestoRepository.count({
      where: [
        { tenantId: ctx.tenantId, transportistaId: id },
        { tenantId: ctx.tenantId, destinoId: id },
      ] as FindOptionsWhere<ManifiestoResiduos>[],
    });

    // Get last service date
    const ultimoManifiesto = await this.manifiestoRepository.findOne({
      where: [
        { tenantId: ctx.tenantId, transportistaId: id },
        { tenantId: ctx.tenantId, destinoId: id },
      ] as FindOptionsWhere<ManifiestoResiduos>[],
      order: { fechaRecoleccion: 'DESC' },
    });

    // Calculate total kg handled (approximate)
    const cantidadResult = await this.manifiestoRepository
      .createQueryBuilder('m')
      .innerJoin('m.detalles', 'd')
      .where('m.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('(m.transportista_id = :proveedorId OR m.destino_id = :proveedorId)', { proveedorId: id })
      .andWhere("d.unidad = 'kg'")
      .select('SUM(d.cantidad)', 'total')
      .getRawOne();

    return {
      ...proveedor,
      totalManifiestos,
      ultimoServicio: ultimoManifiesto?.fechaRecoleccion || null,
      cantidadTotalKg: parseFloat(cantidadResult?.total || '0'),
    };
  }

  async getManifiestosProveedor(
    ctx: ServiceContext,
    proveedorId: string,
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ManifiestoResiduos>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.manifiestoRepository
      .createQueryBuilder('m')
      .leftJoinAndSelect('m.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('m.transportista', 'transportista')
      .leftJoinAndSelect('m.destino', 'destino')
      .where('m.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('(m.transportista_id = :proveedorId OR m.destino_id = :proveedorId)', { proveedorId })
      .orderBy('m.fecha_recoleccion', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  // ========== Alertas ==========
  async getAlertas(ctx: ServiceContext, diasProximoVencer: number = 30): Promise<AlertaProveedor[]> {
    const alertas: AlertaProveedor[] = [];
    const today = new Date();
    const proximaFecha = new Date();
    proximaFecha.setDate(proximaFecha.getDate() + diasProximoVencer);

    // Proveedores activos
    const proveedores = await this.proveedorRepository.find({
      where: { tenantId: ctx.tenantId, activo: true } as FindOptionsWhere<ProveedorAmbiental>,
    });

    for (const proveedor of proveedores) {
      // Sin autorización
      if (!proveedor.numeroAutorizacion) {
        alertas.push({
          proveedorId: proveedor.id,
          razonSocial: proveedor.razonSocial,
          tipo: 'sin_autorizacion',
          mensaje: 'Proveedor sin numero de autorizacion registrado',
          nivel: 'critico',
        });
      }

      // Vencidos
      if (proveedor.fechaVencimiento && proveedor.fechaVencimiento < today) {
        const diasVencido = Math.floor(
          (today.getTime() - proveedor.fechaVencimiento.getTime()) / (1000 * 60 * 60 * 24)
        );
        alertas.push({
          proveedorId: proveedor.id,
          razonSocial: proveedor.razonSocial,
          tipo: 'vencimiento',
          mensaje: `Autorizacion vencida hace ${diasVencido} dias`,
          diasRestantes: -diasVencido,
          nivel: 'critico',
        });
      }
      // Próximos a vencer
      else if (proveedor.fechaVencimiento && proveedor.fechaVencimiento <= proximaFecha) {
        const diasRestantes = Math.floor(
          (proveedor.fechaVencimiento.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)
        );
        alertas.push({
          proveedorId: proveedor.id,
          razonSocial: proveedor.razonSocial,
          tipo: 'vencimiento',
          mensaje: `Autorizacion vence en ${diasRestantes} dias`,
          diasRestantes,
          nivel: 'advertencia',
        });
      }
    }

    // Sort by urgency
    alertas.sort((a, b) => {
      if (a.nivel === 'critico' && b.nivel !== 'critico') return -1;
      if (a.nivel !== 'critico' && b.nivel === 'critico') return 1;
      return (a.diasRestantes ?? 999) - (b.diasRestantes ?? 999);
    });

    return alertas;
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext): Promise<ProveedorStats> {
    const today = new Date();
    const proximaFecha = new Date();
    proximaFecha.setDate(proximaFecha.getDate() + 30);

    const totalProveedores = await this.proveedorRepository.count({
      where: { tenantId: ctx.tenantId } as FindOptionsWhere<ProveedorAmbiental>,
    });

    const proveedoresActivos = await this.proveedorRepository.count({
      where: { tenantId: ctx.tenantId, activo: true } as FindOptionsWhere<ProveedorAmbiental>,
    });

    const proveedoresVencidos = await this.proveedorRepository
      .createQueryBuilder('p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.activo = true')
      .andWhere('p.fecha_vencimiento < :today', { today })
      .getCount();

    const proximosVencer = await this.proveedorRepository
      .createQueryBuilder('p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.activo = true')
      .andWhere('p.fecha_vencimiento >= :today', { today })
      .andWhere('p.fecha_vencimiento <= :proximaFecha', { proximaFecha })
      .getCount();

    const porTipoRaw = await this.proveedorRepository
      .createQueryBuilder('p')
      .select('p.tipo', 'tipo')
      .addSelect('COUNT(*)', 'count')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.activo = true')
      .groupBy('p.tipo')
      .getRawMany();

    const porTipo = porTipoRaw.map((t) => ({
      tipo: t.tipo,
      count: parseInt(t.count, 10),
    }));

    // Top proveedores by manifiestos
    const topProveedoresRaw = await this.manifiestoRepository
      .createQueryBuilder('m')
      .innerJoin('m.transportista', 't')
      .select('t.id', 'id')
      .addSelect('t.razon_social', 'nombre')
      .addSelect('COUNT(*)', 'manifiestos')
      .where('m.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .groupBy('t.id')
      .addGroupBy('t.razon_social')
      .orderBy('COUNT(*)', 'DESC')
      .limit(5)
      .getRawMany();

    const topProveedores = topProveedoresRaw.map((p) => ({
      id: p.id,
      nombre: p.nombre,
      manifiestos: parseInt(p.manifiestos, 10),
    }));

    return {
      totalProveedores,
      proveedoresActivos,
      proveedoresVencidos,
      proximosVencer,
      porTipo,
      topProveedores,
    };
  }

  // ========== Validación para Manifiestos ==========
  async validarParaManifiesto(ctx: ServiceContext, proveedorId: string): Promise<{ valido: boolean; errores: string[] }> {
    const errores: string[] = [];
    const proveedor = await this.findById(ctx, proveedorId);

    if (!proveedor) {
      return { valido: false, errores: ['Proveedor no encontrado'] };
    }

    if (!proveedor.activo) {
      errores.push('Proveedor inactivo');
    }

    if (!proveedor.numeroAutorizacion) {
      errores.push('Proveedor sin numero de autorizacion');
    }

    const today = new Date();
    if (proveedor.fechaVencimiento && proveedor.fechaVencimiento < today) {
      errores.push('Autorizacion del proveedor vencida');
    }

    return {
      valido: errores.length === 0,
      errores,
    };
  }
}
