/**
 * AuditoriaAmbientalService - Servicio para gestión de auditorías ambientales
 *
 * RF-MAA017-006: Gestión de auditorías ambientales con programación,
 * seguimiento de hallazgos y cumplimiento normativo.
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { Auditoria, TipoAuditoria, ResultadoAuditoria } from '../entities/auditoria.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface AuditoriaAmbientalFilters {
  fraccionamientoId?: string;
  tipo?: TipoAuditoria;
  resultado?: ResultadoAuditoria;
  pendiente?: boolean;
  dateFrom?: Date;
  dateTo?: Date;
  anio?: number;
}

export interface CreateAuditoriaAmbientalDto {
  fraccionamientoId: string;
  tipo: TipoAuditoria;
  fechaProgramada: Date;
  auditor?: string;
  observaciones?: string;
}

export interface UpdateAuditoriaAmbientalDto {
  tipo?: TipoAuditoria;
  fechaProgramada?: Date;
  auditor?: string;
  observaciones?: string;
}

export interface CompletarAuditoriaDto {
  resultado: ResultadoAuditoria;
  noConformidades: number;
  observaciones?: string;
  informeUrl?: string;
}

export interface ProgramarAuditoriaSerieDto {
  fraccionamientoId: string;
  tipo: TipoAuditoria;
  fechaInicio: Date;
  periodicidadMeses: number;
  cantidad: number;
  auditor?: string;
}

export interface AuditoriaAmbientalStats {
  totalAuditorias: number;
  auditoriasRealizadas: number;
  auditoriasPendientes: number;
  auditoriasVencidas: number;
  porResultado: { resultado: string; count: number }[];
  porTipo: { tipo: string; count: number }[];
  totalNoConformidades: number;
  promedioNoConformidades: number;
  tendenciaMensual: { mes: string; realizadas: number; noConformidades: number }[];
}

export interface CalendarioAuditoria {
  mes: string;
  auditorias: {
    id: string;
    tipo: TipoAuditoria;
    fechaProgramada: Date;
    fraccionamientoNombre: string;
    auditor: string | null;
    estado: 'programada' | 'realizada' | 'vencida';
    resultado?: ResultadoAuditoria;
  }[];
}

export interface AlertaAuditoria {
  auditoriaId: string;
  tipo: TipoAuditoria;
  fraccionamientoNombre: string;
  tipoAlerta: 'vencida' | 'proxima' | 'sin_auditor';
  mensaje: string;
  diasRestantes?: number;
  nivel: 'advertencia' | 'critico';
}

export class AuditoriaAmbientalService {
  constructor(
    private readonly auditoriaRepository: Repository<Auditoria>
  ) {}

  // ========== CRUD de Auditorías ==========
  async findAll(
    ctx: ServiceContext,
    filters: AuditoriaAmbientalFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<Auditoria>> {
    const skip = (page - 1) * limit;

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

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

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

    if (filters.resultado) {
      queryBuilder.andWhere('a.resultado = :resultado', { resultado: filters.resultado });
    }

    if (filters.pendiente === true) {
      queryBuilder.andWhere('a.resultado IS NULL');
    } else if (filters.pendiente === false) {
      queryBuilder.andWhere('a.resultado IS NOT NULL');
    }

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

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

    if (filters.anio) {
      queryBuilder.andWhere('EXTRACT(YEAR FROM a.fecha_programada) = :anio', { anio: filters.anio });
    }

    queryBuilder
      .orderBy('a.fecha_programada', 'DESC')
      .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<Auditoria | null> {
    return this.auditoriaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<Auditoria>,
      relations: ['fraccionamiento'],
    });
  }

  async findPendientes(ctx: ServiceContext, fraccionamientoId?: string): Promise<Auditoria[]> {
    const queryBuilder = this.auditoriaRepository
      .createQueryBuilder('a')
      .leftJoinAndSelect('a.fraccionamiento', 'fraccionamiento')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.resultado IS NULL');

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

    return queryBuilder.orderBy('a.fecha_programada', 'ASC').getMany();
  }

  async findVencidas(ctx: ServiceContext): Promise<Auditoria[]> {
    const today = new Date();
    return this.auditoriaRepository
      .createQueryBuilder('a')
      .leftJoinAndSelect('a.fraccionamiento', 'fraccionamiento')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.resultado IS NULL')
      .andWhere('a.fecha_programada < :today', { today })
      .orderBy('a.fecha_programada', 'ASC')
      .getMany();
  }

  async create(ctx: ServiceContext, dto: CreateAuditoriaAmbientalDto): Promise<Auditoria> {
    const auditoria = this.auditoriaRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      tipo: dto.tipo,
      fechaProgramada: dto.fechaProgramada,
      auditor: dto.auditor,
      observaciones: dto.observaciones,
    });

    return this.auditoriaRepository.save(auditoria);
  }

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

    if (auditoria.resultado) {
      throw new Error('No se puede modificar una auditoria ya completada');
    }

    if (dto.tipo !== undefined) auditoria.tipo = dto.tipo;
    if (dto.fechaProgramada !== undefined) auditoria.fechaProgramada = dto.fechaProgramada;
    if (dto.auditor !== undefined) auditoria.auditor = dto.auditor;
    if (dto.observaciones !== undefined) auditoria.observaciones = dto.observaciones;

    return this.auditoriaRepository.save(auditoria);
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const auditoria = await this.findById(ctx, id);
    if (!auditoria) return false;

    if (auditoria.resultado) {
      throw new Error('No se puede eliminar una auditoria ya completada');
    }

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

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

  // ========== Completar Auditoría ==========
  async completar(
    ctx: ServiceContext,
    id: string,
    dto: CompletarAuditoriaDto
  ): Promise<Auditoria | null> {
    const auditoria = await this.findById(ctx, id);
    if (!auditoria) return null;

    if (auditoria.resultado) {
      throw new Error('La auditoria ya fue completada');
    }

    auditoria.fechaRealizada = new Date();
    auditoria.resultado = dto.resultado;
    auditoria.noConformidades = dto.noConformidades;
    if (dto.observaciones) auditoria.observaciones = dto.observaciones;
    if (dto.informeUrl) auditoria.informeUrl = dto.informeUrl;

    return this.auditoriaRepository.save(auditoria);
  }

  async asignarAuditor(ctx: ServiceContext, id: string, auditor: string): Promise<Auditoria | null> {
    const auditoria = await this.findById(ctx, id);
    if (!auditoria) return null;

    auditoria.auditor = auditor;
    return this.auditoriaRepository.save(auditoria);
  }

  async reprogramar(ctx: ServiceContext, id: string, nuevaFecha: Date): Promise<Auditoria | null> {
    const auditoria = await this.findById(ctx, id);
    if (!auditoria) return null;

    if (auditoria.resultado) {
      throw new Error('No se puede reprogramar una auditoria ya completada');
    }

    auditoria.fechaProgramada = nuevaFecha;
    return this.auditoriaRepository.save(auditoria);
  }

  // ========== Programación en Serie ==========
  async programarSerie(ctx: ServiceContext, dto: ProgramarAuditoriaSerieDto): Promise<Auditoria[]> {
    const auditorias: Auditoria[] = [];
    let fecha = new Date(dto.fechaInicio);

    for (let i = 0; i < dto.cantidad; i++) {
      const auditoria = this.auditoriaRepository.create({
        tenantId: ctx.tenantId,
        fraccionamientoId: dto.fraccionamientoId,
        tipo: dto.tipo,
        fechaProgramada: new Date(fecha),
        auditor: dto.auditor,
      });

      const saved = await this.auditoriaRepository.save(auditoria);
      auditorias.push(saved);

      // Advance to next date
      fecha.setMonth(fecha.getMonth() + dto.periodicidadMeses);
    }

    return auditorias;
  }

  // ========== Calendario ==========
  async getCalendario(ctx: ServiceContext, anio: number, fraccionamientoId?: string): Promise<CalendarioAuditoria[]> {
    const queryBuilder = this.auditoriaRepository
      .createQueryBuilder('a')
      .leftJoinAndSelect('a.fraccionamiento', 'fraccionamiento')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('EXTRACT(YEAR FROM a.fecha_programada) = :anio', { anio });

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

    const auditorias = await queryBuilder.orderBy('a.fecha_programada', 'ASC').getMany();

    const today = new Date();
    const calendario: CalendarioAuditoria[] = [];

    // Group by month
    const meses: Record<string, CalendarioAuditoria['auditorias']> = {};

    for (const audit of auditorias) {
      const fecha = new Date(audit.fechaProgramada);
      const mesKey = `${fecha.getFullYear()}-${String(fecha.getMonth() + 1).padStart(2, '0')}`;

      if (!meses[mesKey]) {
        meses[mesKey] = [];
      }

      let estado: 'programada' | 'realizada' | 'vencida' = 'programada';
      if (audit.resultado) {
        estado = 'realizada';
      } else if (fecha < today) {
        estado = 'vencida';
      }

      meses[mesKey].push({
        id: audit.id,
        tipo: audit.tipo,
        fechaProgramada: audit.fechaProgramada,
        fraccionamientoNombre: audit.fraccionamiento?.nombre || 'Sin asignar',
        auditor: audit.auditor,
        estado,
        resultado: audit.resultado,
      });
    }

    // Convert to array
    for (const [mes, auditoriasDelMes] of Object.entries(meses)) {
      calendario.push({ mes, auditorias: auditoriasDelMes });
    }

    return calendario;
  }

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

    const pendientes = await this.auditoriaRepository
      .createQueryBuilder('a')
      .leftJoinAndSelect('a.fraccionamiento', 'fraccionamiento')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.resultado IS NULL')
      .getMany();

    for (const auditoria of pendientes) {
      const fecha = new Date(auditoria.fechaProgramada);
      const fracNombre = auditoria.fraccionamiento?.nombre || 'Sin asignar';

      // Vencidas
      if (fecha < today) {
        const diasVencido = Math.floor((today.getTime() - fecha.getTime()) / (1000 * 60 * 60 * 24));
        alertas.push({
          auditoriaId: auditoria.id,
          tipo: auditoria.tipo,
          fraccionamientoNombre: fracNombre,
          tipoAlerta: 'vencida',
          mensaje: `Auditoria ${auditoria.tipo} vencida hace ${diasVencido} dias`,
          diasRestantes: -diasVencido,
          nivel: 'critico',
        });
      }
      // Próximas
      else if (fecha <= proximaFecha) {
        const diasRestantes = Math.floor((fecha.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
        alertas.push({
          auditoriaId: auditoria.id,
          tipo: auditoria.tipo,
          fraccionamientoNombre: fracNombre,
          tipoAlerta: 'proxima',
          mensaje: `Auditoria ${auditoria.tipo} programada en ${diasRestantes} dias`,
          diasRestantes,
          nivel: 'advertencia',
        });
      }

      // Sin auditor
      if (!auditoria.auditor && fecha >= today) {
        alertas.push({
          auditoriaId: auditoria.id,
          tipo: auditoria.tipo,
          fraccionamientoNombre: fracNombre,
          tipoAlerta: 'sin_auditor',
          mensaje: `Auditoria ${auditoria.tipo} sin auditor asignado`,
          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, fraccionamientoId?: string, anio?: number): Promise<AuditoriaAmbientalStats> {
    const baseQuery = () => {
      const qb = this.auditoriaRepository
        .createQueryBuilder('a')
        .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId });

      if (fraccionamientoId) {
        qb.andWhere('a.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
      }
      if (anio) {
        qb.andWhere('EXTRACT(YEAR FROM a.fecha_programada) = :anio', { anio });
      }
      return qb;
    };

    const today = new Date();

    const totalAuditorias = await baseQuery().getCount();

    const auditoriasRealizadas = await baseQuery()
      .andWhere('a.resultado IS NOT NULL')
      .getCount();

    const auditoriasPendientes = await baseQuery()
      .andWhere('a.resultado IS NULL')
      .andWhere('a.fecha_programada >= :today', { today })
      .getCount();

    const auditoriasVencidas = await baseQuery()
      .andWhere('a.resultado IS NULL')
      .andWhere('a.fecha_programada < :today', { today })
      .getCount();

    // Por resultado
    const porResultadoRaw = await baseQuery()
      .andWhere('a.resultado IS NOT NULL')
      .select('a.resultado', 'resultado')
      .addSelect('COUNT(*)', 'count')
      .groupBy('a.resultado')
      .getRawMany();

    const porResultado = porResultadoRaw.map((r) => ({
      resultado: r.resultado,
      count: parseInt(r.count, 10),
    }));

    // Por tipo
    const porTipoRaw = await baseQuery()
      .select('a.tipo', 'tipo')
      .addSelect('COUNT(*)', 'count')
      .groupBy('a.tipo')
      .getRawMany();

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

    // No conformidades
    const noConfResult = await baseQuery()
      .andWhere('a.resultado IS NOT NULL')
      .select('SUM(a.no_conformidades)', 'total')
      .addSelect('AVG(a.no_conformidades)', 'promedio')
      .getRawOne();

    const totalNoConformidades = parseInt(noConfResult?.total || '0', 10);
    const promedioNoConformidades = parseFloat(noConfResult?.promedio || '0');

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

    const tendenciaRaw = await this.auditoriaRepository
      .createQueryBuilder('a')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.fecha_programada >= :doceMesesAtras', { doceMesesAtras })
      .andWhere('a.resultado IS NOT NULL')
      .select("TO_CHAR(a.fecha_realizada, 'YYYY-MM')", 'mes')
      .addSelect('COUNT(*)', 'realizadas')
      .addSelect('SUM(a.no_conformidades)', 'noConformidades')
      .groupBy("TO_CHAR(a.fecha_realizada, 'YYYY-MM')")
      .orderBy("TO_CHAR(a.fecha_realizada, 'YYYY-MM')", 'ASC')
      .getRawMany();

    const tendenciaMensual = tendenciaRaw.map((t) => ({
      mes: t.mes,
      realizadas: parseInt(t.realizadas, 10),
      noConformidades: parseInt(t.noConformidades || '0', 10),
    }));

    return {
      totalAuditorias,
      auditoriasRealizadas,
      auditoriasPendientes,
      auditoriasVencidas,
      porResultado,
      porTipo,
      totalNoConformidades,
      promedioNoConformidades: Math.round(promedioNoConformidades * 100) / 100,
      tendenciaMensual,
    };
  }

  // ========== Historial por Fraccionamiento ==========
  async getHistorialFraccionamiento(
    ctx: ServiceContext,
    fraccionamientoId: string
  ): Promise<Auditoria[]> {
    return this.auditoriaRepository.find({
      where: {
        tenantId: ctx.tenantId,
        fraccionamientoId,
      } as FindOptionsWhere<Auditoria>,
      order: { fechaProgramada: 'DESC' },
    });
  }

  // ========== Comparativa entre Obras ==========
  async getComparativaObras(ctx: ServiceContext, anio: number): Promise<{
    fraccionamientoId: string;
    fraccionamientoNombre: string;
    totalAuditorias: number;
    aprobadas: number;
    noAprobadas: number;
    porcentajeAprobacion: number;
    totalNoConformidades: number;
  }[]> {
    const result = await this.auditoriaRepository
      .createQueryBuilder('a')
      .leftJoin('a.fraccionamiento', 'f')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('EXTRACT(YEAR FROM a.fecha_programada) = :anio', { anio })
      .andWhere('a.resultado IS NOT NULL')
      .select('a.fraccionamiento_id', 'fraccionamientoId')
      .addSelect('f.nombre', 'fraccionamientoNombre')
      .addSelect('COUNT(*)', 'totalAuditorias')
      .addSelect("SUM(CASE WHEN a.resultado IN ('aprobada', 'aprobada_observaciones') THEN 1 ELSE 0 END)", 'aprobadas')
      .addSelect("SUM(CASE WHEN a.resultado = 'no_aprobada' THEN 1 ELSE 0 END)", 'noAprobadas')
      .addSelect('SUM(a.no_conformidades)', 'totalNoConformidades')
      .groupBy('a.fraccionamiento_id')
      .addGroupBy('f.nombre')
      .getRawMany();

    return result.map((r) => {
      const total = parseInt(r.totalAuditorias, 10);
      const aprobadas = parseInt(r.aprobadas, 10);
      return {
        fraccionamientoId: r.fraccionamientoId,
        fraccionamientoNombre: r.fraccionamientoNombre || 'Sin nombre',
        totalAuditorias: total,
        aprobadas,
        noAprobadas: parseInt(r.noAprobadas, 10),
        porcentajeAprobacion: total > 0 ? Math.round((aprobadas / total) * 100) : 0,
        totalNoConformidades: parseInt(r.totalNoConformidades || '0', 10),
      };
    });
  }
}
