/**
 * AmbientalService - Servicio para gestión ambiental
 *
 * RF-MAA017-006: Gestión de residuos, manifiestos e impactos
 *
 * @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 { ProveedorAmbiental } from '../entities/proveedor-ambiental.entity';
import { ManifiestoResiduos, EstadoManifiesto } from '../entities/manifiesto-residuos.entity';
import { ManifiestoDetalle } from '../entities/manifiesto-detalle.entity';
import { ImpactoAmbiental, TipoImpacto, NivelRiesgo, EstadoImpacto } from '../entities/impacto-ambiental.entity';
import { QuejaAmbiental, OrigenQueja, TipoQueja, EstadoQueja } from '../entities/queja-ambiental.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface ResiduoFilters {
  categoria?: CategoriaResiduo;
  activo?: boolean;
  search?: string;
}

export interface GeneracionFilters {
  fraccionamientoId?: string;
  residuoId?: string;
  estado?: EstadoResiduo;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface ManifiestoFilters {
  fraccionamientoId?: string;
  estado?: EstadoManifiesto;
  transportistaId?: string;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface ImpactoFilters {
  fraccionamientoId?: string;
  tipoImpacto?: TipoImpacto;
  nivelRiesgo?: NivelRiesgo;
  estado?: EstadoImpacto;
}

export interface QuejaFilters {
  fraccionamientoId?: string;
  tipo?: TipoQueja;
  estado?: EstadoQueja;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface CreateResiduoCatalogoDto {
  codigo: string;
  nombre: string;
  categoria: CategoriaResiduo;
  caracteristicasCretib?: string;
  normaReferencia?: string;
  manejoRequerido?: string;
  tiempoMaxAlmacenDias?: number;
  requiereManifiesto?: boolean;
}

export interface CreateGeneracionDto {
  fraccionamientoId: string;
  residuoId: string;
  fechaGeneracion: Date;
  cantidad: number;
  unidad: UnidadResiduo;
  areaGeneracion?: string;
  fuente?: string;
  contenedorId?: string;
}

export interface CreateManifiestoDto {
  fraccionamientoId: string;
  transportistaId: string;
  destinoId: string;
  fechaRecoleccion: Date;
  observaciones?: string;
}

export interface AddDetalleManifiestoDto {
  residuoId: string;
  generacionIds?: string[];
  cantidad: number;
  unidad: UnidadResiduo;
  descripcion?: string;
}

export interface CreateImpactoDto {
  fraccionamientoId: string;
  aspecto: string;
  tipoImpacto: TipoImpacto;
  severidad: 'bajo' | 'medio' | 'alto';
  probabilidad: 'baja' | 'media' | 'alta';
  medidasMitigacion?: string;
  responsableId?: string;
}

export interface CreateQuejaDto {
  fraccionamientoId: string;
  origen: OrigenQueja;
  tipo: TipoQueja;
  descripcion: string;
  nombreQuejoso?: string;
  contactoQuejoso?: string;
}

export interface AmbientalStats {
  residuosGenerados: number;
  residuosPendientes: number;
  manifiestosPendientes: number;
  impactosIdentificados: number;
  impactosSignificativos: number;
  quejasAbiertas: number;
  residuosPorCategoria: { categoria: string; cantidad: number }[];
}

export class AmbientalService {
  constructor(
    private readonly residuoCatalogoRepository: Repository<ResiduoCatalogo>,
    private readonly generacionRepository: Repository<ResiduoGeneracion>,
    private readonly almacenRepository: Repository<AlmacenTemporal>,
    private readonly proveedorRepository: Repository<ProveedorAmbiental>,
    private readonly manifiestoRepository: Repository<ManifiestoResiduos>,
    private readonly detalleRepository: Repository<ManifiestoDetalle>,
    private readonly impactoRepository: Repository<ImpactoAmbiental>,
    private readonly quejaRepository: Repository<QuejaAmbiental>
  ) {}

  private generateFolio(prefix: string): string {
    const now = new Date();
    const year = now.getFullYear().toString().slice(-2);
    const month = (now.getMonth() + 1).toString().padStart(2, '0');
    const random = Math.random().toString(36).substring(2, 6).toUpperCase();
    return `${prefix}-${year}${month}-${random}`;
  }

  // ========== Catálogo de Residuos ==========
  async findResiduos(
    _ctx: ServiceContext,
    filters: ResiduoFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ResiduoCatalogo>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.residuoCatalogoRepository
      .createQueryBuilder('r');

    if (filters.categoria) {
      queryBuilder.andWhere('r.categoria = :categoria', { categoria: filters.categoria });
    }

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

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

    queryBuilder
      .orderBy('r.nombre', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  async createResiduo(dto: CreateResiduoCatalogoDto): Promise<ResiduoCatalogo> {
    const residuo = this.residuoCatalogoRepository.create({
      codigo: dto.codigo,
      nombre: dto.nombre,
      categoria: dto.categoria,
      caracteristicasCretib: dto.caracteristicasCretib,
      normaReferencia: dto.normaReferencia,
      manejoRequerido: dto.manejoRequerido,
      tiempoMaxAlmacenDias: dto.tiempoMaxAlmacenDias,
      requiereManifiesto: dto.requiereManifiesto ?? false,
      activo: true,
    });
    return this.residuoCatalogoRepository.save(residuo);
  }

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

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

    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.dateFrom) {
      queryBuilder.andWhere('g.fecha_generacion >= :dateFrom', { dateFrom: filters.dateFrom });
    }

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

    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 createGeneracion(ctx: ServiceContext, dto: CreateGeneracionDto): Promise<ResiduoGeneracion> {
    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,
      estado: 'almacenado',
      createdById: ctx.userId,
    });

    return this.generacionRepository.save(generacion);
  }

  async updateGeneracionEstado(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);
  }

  // ========== Almacenes Temporales ==========
  async findAlmacenes(ctx: ServiceContext, fraccionamientoId?: string): Promise<AlmacenTemporal[]> {
    const where: FindOptionsWhere<AlmacenTemporal> = { tenantId: ctx.tenantId };
    if (fraccionamientoId) {
      where.fraccionamientoId = fraccionamientoId;
    }

    return this.almacenRepository.find({
      where,
      relations: ['fraccionamiento'],
    });
  }

  // ========== Proveedores Ambientales ==========
  async findProveedores(ctx: ServiceContext): Promise<ProveedorAmbiental[]> {
    return this.proveedorRepository.find({
      where: { tenantId: ctx.tenantId, activo: true } as FindOptionsWhere<ProveedorAmbiental>,
    });
  }

  // ========== Manifiestos ==========
  async findManifiestos(
    ctx: ServiceContext,
    filters: ManifiestoFilters = {},
    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 });

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

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

    if (filters.transportistaId) {
      queryBuilder.andWhere('m.transportista_id = :transportistaId', { transportistaId: filters.transportistaId });
    }

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

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

    queryBuilder
      .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),
    };
  }

  async findManifiestoWithDetalles(ctx: ServiceContext, id: string): Promise<ManifiestoResiduos | null> {
    return this.manifiestoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ManifiestoResiduos>,
      relations: ['fraccionamiento', 'transportista', 'destino', 'detalles', 'detalles.residuo'],
    });
  }

  async createManifiesto(ctx: ServiceContext, dto: CreateManifiestoDto): Promise<ManifiestoResiduos> {
    const manifiesto = this.manifiestoRepository.create({
      tenantId: ctx.tenantId,
      folio: this.generateFolio('MAN'),
      fraccionamientoId: dto.fraccionamientoId,
      transportistaId: dto.transportistaId,
      destinoId: dto.destinoId,
      fechaRecoleccion: dto.fechaRecoleccion,
      observaciones: dto.observaciones,
      estado: 'emitido',
    });

    return this.manifiestoRepository.save(manifiesto);
  }

  async addDetalleManifiesto(ctx: ServiceContext, manifiestoId: string, dto: AddDetalleManifiestoDto): Promise<ManifiestoDetalle> {
    const manifiesto = await this.manifiestoRepository.findOne({
      where: { id: manifiestoId, tenantId: ctx.tenantId } as FindOptionsWhere<ManifiestoResiduos>,
    });
    if (!manifiesto) throw new Error('Manifiesto no encontrado');

    const detalle = this.detalleRepository.create({
      manifiestoId,
      residuoId: dto.residuoId,
      generacionIds: dto.generacionIds,
      cantidad: dto.cantidad,
      unidad: dto.unidad,
      descripcion: dto.descripcion,
    });

    return this.detalleRepository.save(detalle);
  }

  async updateManifiestoEstado(ctx: ServiceContext, id: string, estado: EstadoManifiesto): Promise<ManifiestoResiduos | null> {
    const manifiesto = await this.manifiestoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ManifiestoResiduos>,
    });
    if (!manifiesto) return null;

    manifiesto.estado = estado;
    if (estado === 'entregado') {
      manifiesto.fechaEntrega = new Date();
    }

    return this.manifiestoRepository.save(manifiesto);
  }

  // ========== Impactos Ambientales ==========
  async findImpactos(
    ctx: ServiceContext,
    filters: ImpactoFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ImpactoAmbiental>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.impactoRepository
      .createQueryBuilder('i')
      .leftJoinAndSelect('i.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('i.responsable', 'responsable')
      .where('i.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    if (filters.tipoImpacto) {
      queryBuilder.andWhere('i.tipo_impacto = :tipoImpacto', { tipoImpacto: filters.tipoImpacto });
    }

    if (filters.nivelRiesgo) {
      queryBuilder.andWhere('i.nivel_riesgo = :nivelRiesgo', { nivelRiesgo: filters.nivelRiesgo });
    }

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

    queryBuilder
      .orderBy('i.created_at', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  async createImpacto(ctx: ServiceContext, dto: CreateImpactoDto): Promise<ImpactoAmbiental> {
    // Calculate risk level
    const riskMatrix: Record<string, Record<string, NivelRiesgo>> = {
      bajo: { baja: 'tolerable', media: 'tolerable', alta: 'moderado' },
      medio: { baja: 'tolerable', media: 'moderado', alta: 'significativo' },
      alto: { baja: 'moderado', media: 'significativo', alta: 'significativo' },
    };

    const nivelRiesgo = riskMatrix[dto.severidad][dto.probabilidad];

    const impacto = this.impactoRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      aspecto: dto.aspecto,
      tipoImpacto: dto.tipoImpacto,
      severidad: dto.severidad,
      probabilidad: dto.probabilidad,
      nivelRiesgo,
      medidasMitigacion: dto.medidasMitigacion,
      responsableId: dto.responsableId,
      estado: 'identificado',
    });

    return this.impactoRepository.save(impacto);
  }

  async updateImpactoEstado(ctx: ServiceContext, id: string, estado: EstadoImpacto): Promise<ImpactoAmbiental | null> {
    const impacto = await this.impactoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ImpactoAmbiental>,
    });
    if (!impacto) return null;

    impacto.estado = estado;
    return this.impactoRepository.save(impacto);
  }

  // ========== Quejas Ambientales ==========
  async findQuejas(
    ctx: ServiceContext,
    filters: QuejaFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<QuejaAmbiental>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.quejaRepository
      .createQueryBuilder('q')
      .leftJoinAndSelect('q.fraccionamiento', 'fraccionamiento')
      .where('q.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

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

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

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

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

    queryBuilder
      .orderBy('q.fecha_queja', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  async createQueja(ctx: ServiceContext, dto: CreateQuejaDto): Promise<QuejaAmbiental> {
    const queja = this.quejaRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      fechaQueja: new Date(),
      origen: dto.origen,
      tipo: dto.tipo,
      descripcion: dto.descripcion,
      nombreQuejoso: dto.nombreQuejoso,
      contactoQuejoso: dto.contactoQuejoso,
      estado: 'recibida',
    });

    return this.quejaRepository.save(queja);
  }

  async atenderQueja(ctx: ServiceContext, id: string, accionesTomadas: string): Promise<QuejaAmbiental | null> {
    const queja = await this.quejaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<QuejaAmbiental>,
    });
    if (!queja) return null;

    queja.accionesTomadas = accionesTomadas;
    queja.estado = 'atendiendo';

    return this.quejaRepository.save(queja);
  }

  async cerrarQueja(ctx: ServiceContext, id: string): Promise<QuejaAmbiental | null> {
    const queja = await this.quejaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<QuejaAmbiental>,
    });
    if (!queja) return null;

    queja.fechaCierre = new Date();
    queja.estado = 'cerrada';

    return this.quejaRepository.save(queja);
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<AmbientalStats> {
    const genQuery = this.generacionRepository
      .createQueryBuilder('g')
      .where('g.tenant_id = :tenantId', { tenantId: ctx.tenantId });
    if (fraccionamientoId) {
      genQuery.andWhere('g.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }
    const residuosGenerados = await genQuery.getCount();

    const residuosPendientes = await this.generacionRepository
      .createQueryBuilder('g')
      .where('g.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('g.estado = :estado', { estado: 'almacenado' })
      .getCount();

    const manifiestosPendientes = await this.manifiestoRepository
      .createQueryBuilder('m')
      .where('m.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('m.estado IN (:...estados)', { estados: ['emitido', 'en_transito'] })
      .getCount();

    const impactosIdentificados = await this.impactoRepository.count({
      where: { tenantId: ctx.tenantId } as FindOptionsWhere<ImpactoAmbiental>,
    });

    const impactosSignificativos = await this.impactoRepository.count({
      where: { tenantId: ctx.tenantId, nivelRiesgo: 'significativo' } as FindOptionsWhere<ImpactoAmbiental>,
    });

    const quejasAbiertas = await this.quejaRepository
      .createQueryBuilder('q')
      .where('q.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('q.estado != :cerrada', { cerrada: 'cerrada' })
      .getCount();

    // Residuos por categoría
    const residuosPorCategoria = await this.generacionRepository
      .createQueryBuilder('g')
      .innerJoin('g.residuo', 'r')
      .select('r.categoria', 'categoria')
      .addSelect('SUM(g.cantidad)', 'cantidad')
      .where('g.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .groupBy('r.categoria')
      .getRawMany();

    return {
      residuosGenerados,
      residuosPendientes,
      manifiestosPendientes,
      impactosIdentificados,
      impactosSignificativos,
      quejasAbiertas,
      residuosPorCategoria: residuosPorCategoria.map((r) => ({
        categoria: r.categoria,
        cantidad: parseFloat(r.cantidad || '0'),
      })),
    };
  }
}
