/**
 * StpsService - Servicio para cumplimiento STPS
 *
 * RF-MAA017-005: Gestión de normas, comisiones y auditorías
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { NormaStps } from '../entities/norma-stps.entity';
import { NormaRequisito } from '../entities/norma-requisito.entity';
import { CumplimientoObra, EstadoCumplimiento } from '../entities/cumplimiento-obra.entity';
import { ComisionSeguridad, EstadoComision } from '../entities/comision-seguridad.entity';
import { ComisionIntegrante, RolComision, Representacion } from '../entities/comision-integrante.entity';
import { ComisionRecorrido } from '../entities/comision-recorrido.entity';
import { ProgramaSeguridad } from '../entities/programa-seguridad.entity';
import { ProgramaActividad, TipoActividadPrograma, EstadoActividad } from '../entities/programa-actividad.entity';
import { DocumentoStps, TipoDocumentoStps } from '../entities/documento-stps.entity';
import { Auditoria, TipoAuditoria, ResultadoAuditoria } from '../entities/auditoria.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface CumplimientoFilters {
  fraccionamientoId?: string;
  normaId?: string;
  estado?: EstadoCumplimiento;
}

export interface ComisionFilters {
  fraccionamientoId?: string;
  estado?: EstadoComision;
}

export interface AuditoriaFilters {
  fraccionamientoId?: string;
  tipo?: TipoAuditoria;
  resultado?: ResultadoAuditoria;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface CreateCumplimientoDto {
  fraccionamientoId: string;
  normaId: string;
  requisitoId?: string;
  evaluadorId?: string;
  fechaEvaluacion: Date;
  estado?: EstadoCumplimiento;
  evidenciaUrl?: string;
  observaciones?: string;
  fechaCompromiso?: Date;
}

export interface CreateComisionDto {
  fraccionamientoId: string;
  fechaConstitucion: Date;
  numeroActa?: string;
  vigenciaInicio: Date;
  vigenciaFin: Date;
  documentoActaUrl?: string;
}

export interface AddIntegranteDto {
  employeeId: string;
  rol: RolComision;
  representacion: Representacion;
  fechaNombramiento: Date;
}

export interface CreateRecorridoDto {
  comisionId: string;
  fechaProgramada: Date;
  areasRecorridas?: string;
}

export interface CreateProgramaDto {
  fraccionamientoId: string;
  anio: number;
  objetivoGeneral?: string;
  metas?: Record<string, unknown>;
  presupuesto?: number;
}

export interface CreateActividadDto {
  programaId: string;
  actividad: string;
  tipo: TipoActividadPrograma;
  fechaProgramada: Date;
  responsableId?: string;
  recursos?: string;
}

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

export interface CreateDocumentoDto {
  tipo: TipoDocumentoStps;
  folio: string;
  fraccionamientoId?: string;
  employeeId?: string;
  fechaEmision: Date;
  fechaVencimiento?: Date;
  datosDocumento?: Record<string, unknown>;
  documentoUrl?: string;
}

export interface StpsStats {
  totalNormas: number;
  cumplimientoPorEstado: { estado: string; count: number }[];
  porcentajeCumplimiento: number;
  comisionesActivas: number;
  recorridosPendientes: number;
  actividadesPendientes: number;
  auditoriasProgramadas: number;
}

export class StpsService {
  constructor(
    private readonly normaRepository: Repository<NormaStps>,
    private readonly requisitoRepository: Repository<NormaRequisito>,
    private readonly cumplimientoRepository: Repository<CumplimientoObra>,
    private readonly comisionRepository: Repository<ComisionSeguridad>,
    private readonly integranteRepository: Repository<ComisionIntegrante>,
    private readonly recorridoRepository: Repository<ComisionRecorrido>,
    private readonly programaRepository: Repository<ProgramaSeguridad>,
    private readonly actividadRepository: Repository<ProgramaActividad>,
    private readonly documentoRepository: Repository<DocumentoStps>,
    private readonly auditoriaRepository: Repository<Auditoria>
  ) {}

  // ========== Normas y Requisitos ==========
  async findNormas(): Promise<NormaStps[]> {
    // NormaStps is a shared catalog (no tenant_id)
    return this.normaRepository.find({
      where: { activo: true } as FindOptionsWhere<NormaStps>,
      order: { codigo: 'ASC' },
    });
  }

  async findNormaById(id: string): Promise<NormaStps | null> {
    return this.normaRepository.findOne({
      where: { id } as FindOptionsWhere<NormaStps>,
      relations: ['requisitos'],
    });
  }

  async findRequisitosByNorma(normaId: string): Promise<NormaRequisito[]> {
    return this.requisitoRepository.find({
      where: { normaId } as FindOptionsWhere<NormaRequisito>,
      order: { numero: 'ASC' },
    });
  }

  // ========== Cumplimiento por Obra ==========
  async findCumplimientos(
    ctx: ServiceContext,
    filters: CumplimientoFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<CumplimientoObra>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.cumplimientoRepository
      .createQueryBuilder('c')
      .leftJoinAndSelect('c.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('c.norma', 'norma')
      .leftJoinAndSelect('c.requisito', 'requisito')
      .leftJoinAndSelect('c.evaluador', 'evaluador')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    if (filters.normaId) {
      queryBuilder.andWhere('c.norma_id = :normaId', { normaId: filters.normaId });
    }

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

    queryBuilder
      .orderBy('norma.codigo', 'ASC')
      .addOrderBy('requisito.numero', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  async createCumplimiento(ctx: ServiceContext, dto: CreateCumplimientoDto): Promise<CumplimientoObra> {
    const cumplimiento = this.cumplimientoRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      normaId: dto.normaId,
      requisitoId: dto.requisitoId,
      evaluadorId: dto.evaluadorId,
      fechaEvaluacion: dto.fechaEvaluacion,
      estado: dto.estado || 'no_cumple',
      evidenciaUrl: dto.evidenciaUrl,
      observaciones: dto.observaciones,
      fechaCompromiso: dto.fechaCompromiso,
    });

    return this.cumplimientoRepository.save(cumplimiento);
  }

  async updateCumplimientoEstado(
    ctx: ServiceContext,
    id: string,
    estado: EstadoCumplimiento,
    evidenciaUrl?: string,
    observaciones?: string
  ): Promise<CumplimientoObra | null> {
    const cumplimiento = await this.cumplimientoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<CumplimientoObra>,
    });
    if (!cumplimiento) return null;

    cumplimiento.estado = estado;
    cumplimiento.fechaEvaluacion = new Date();
    if (evidenciaUrl) cumplimiento.evidenciaUrl = evidenciaUrl;
    if (observaciones) cumplimiento.observaciones = observaciones;

    return this.cumplimientoRepository.save(cumplimiento);
  }

  // ========== Comisiones de Seguridad ==========
  async findComisiones(
    ctx: ServiceContext,
    filters: ComisionFilters = {}
  ): Promise<ComisionSeguridad[]> {
    const queryBuilder = this.comisionRepository
      .createQueryBuilder('c')
      .leftJoinAndSelect('c.fraccionamiento', 'fraccionamiento')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

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

    return queryBuilder.orderBy('c.fecha_constitucion', 'DESC').getMany();
  }

  async findComisionById(ctx: ServiceContext, id: string): Promise<ComisionSeguridad | null> {
    return this.comisionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ComisionSeguridad>,
      relations: ['fraccionamiento', 'integrantes', 'integrantes.employee', 'recorridos'],
    });
  }

  async createComision(ctx: ServiceContext, dto: CreateComisionDto): Promise<ComisionSeguridad> {
    const comision = this.comisionRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      fechaConstitucion: dto.fechaConstitucion,
      numeroActa: dto.numeroActa,
      vigenciaInicio: dto.vigenciaInicio,
      vigenciaFin: dto.vigenciaFin,
      documentoActaUrl: dto.documentoActaUrl,
      estado: 'activa',
    });

    return this.comisionRepository.save(comision);
  }

  async updateComisionEstado(ctx: ServiceContext, id: string, estado: EstadoComision): Promise<ComisionSeguridad | null> {
    const comision = await this.comisionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ComisionSeguridad>,
    });
    if (!comision) return null;

    comision.estado = estado;
    return this.comisionRepository.save(comision);
  }

  async addIntegrante(_ctx: ServiceContext, comisionId: string, dto: AddIntegranteDto): Promise<ComisionIntegrante> {
    const integrante = this.integranteRepository.create({
      comisionId,
      employeeId: dto.employeeId,
      rol: dto.rol,
      representacion: dto.representacion,
      fechaNombramiento: dto.fechaNombramiento,
      activo: true,
    });

    return this.integranteRepository.save(integrante);
  }

  async removeIntegrante(_ctx: ServiceContext, integranteId: string): Promise<boolean> {
    const integrante = await this.integranteRepository.findOne({
      where: { id: integranteId } as FindOptionsWhere<ComisionIntegrante>,
    });
    if (!integrante) return false;

    integrante.activo = false;
    await this.integranteRepository.save(integrante);
    return true;
  }

  async createRecorrido(_ctx: ServiceContext, dto: CreateRecorridoDto): Promise<ComisionRecorrido> {
    const recorrido = this.recorridoRepository.create({
      comisionId: dto.comisionId,
      fechaProgramada: dto.fechaProgramada,
      areasRecorridas: dto.areasRecorridas,
      estado: 'programado',
    });

    return this.recorridoRepository.save(recorrido);
  }

  async completarRecorrido(
    _ctx: ServiceContext,
    id: string,
    hallazgos?: string,
    recomendaciones?: string,
    numeroActa?: string,
    documentoActaUrl?: string
  ): Promise<ComisionRecorrido | null> {
    const recorrido = await this.recorridoRepository.findOne({
      where: { id } as FindOptionsWhere<ComisionRecorrido>,
    });
    if (!recorrido) return null;

    recorrido.fechaRealizada = new Date();
    if (hallazgos) recorrido.hallazgos = hallazgos;
    if (recomendaciones) recorrido.recomendaciones = recomendaciones;
    if (numeroActa) recorrido.numeroActa = numeroActa;
    if (documentoActaUrl) recorrido.documentoActaUrl = documentoActaUrl;
    recorrido.estado = 'realizado';

    return this.recorridoRepository.save(recorrido);
  }

  async cancelarRecorrido(_ctx: ServiceContext, id: string): Promise<ComisionRecorrido | null> {
    const recorrido = await this.recorridoRepository.findOne({
      where: { id } as FindOptionsWhere<ComisionRecorrido>,
    });
    if (!recorrido) return null;

    recorrido.estado = 'cancelado';
    return this.recorridoRepository.save(recorrido);
  }

  // ========== Programas de Seguridad ==========
  async findProgramas(ctx: ServiceContext, fraccionamientoId?: string, anio?: number): Promise<ProgramaSeguridad[]> {
    const queryBuilder = this.programaRepository
      .createQueryBuilder('p')
      .leftJoinAndSelect('p.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('p.aprobadoPor', 'aprobadoPor')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

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

    return queryBuilder.orderBy('p.anio', 'DESC').getMany();
  }

  async findProgramaById(ctx: ServiceContext, id: string): Promise<ProgramaSeguridad | null> {
    return this.programaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ProgramaSeguridad>,
      relations: ['fraccionamiento', 'aprobadoPor', 'actividades', 'actividades.responsable'],
    });
  }

  async createPrograma(ctx: ServiceContext, dto: CreateProgramaDto): Promise<ProgramaSeguridad> {
    const programa = this.programaRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      anio: dto.anio,
      objetivoGeneral: dto.objetivoGeneral,
      metas: dto.metas,
      presupuesto: dto.presupuesto,
      estado: 'borrador',
    });

    return this.programaRepository.save(programa);
  }

  async aprobarPrograma(ctx: ServiceContext, id: string): Promise<ProgramaSeguridad | null> {
    const programa = await this.programaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ProgramaSeguridad>,
    });
    if (!programa) return null;

    programa.estado = 'activo';
    if (ctx.userId) programa.aprobadoPorId = ctx.userId;
    programa.fechaAprobacion = new Date();
    return this.programaRepository.save(programa);
  }

  async finalizarPrograma(ctx: ServiceContext, id: string): Promise<ProgramaSeguridad | null> {
    const programa = await this.programaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ProgramaSeguridad>,
    });
    if (!programa) return null;

    programa.estado = 'finalizado';
    return this.programaRepository.save(programa);
  }

  async createActividad(_ctx: ServiceContext, dto: CreateActividadDto): Promise<ProgramaActividad> {
    const actividad = this.actividadRepository.create({
      programaId: dto.programaId,
      actividad: dto.actividad,
      tipo: dto.tipo,
      fechaProgramada: dto.fechaProgramada,
      responsableId: dto.responsableId,
      recursos: dto.recursos,
      estado: 'pendiente',
    });

    return this.actividadRepository.save(actividad);
  }

  async updateActividadEstado(_ctx: ServiceContext, id: string, estado: EstadoActividad): Promise<ProgramaActividad | null> {
    const actividad = await this.actividadRepository.findOne({
      where: { id } as FindOptionsWhere<ProgramaActividad>,
    });
    if (!actividad) return null;

    actividad.estado = estado;
    if (estado === 'completada') {
      actividad.fechaRealizada = new Date();
    }
    return this.actividadRepository.save(actividad);
  }

  async completarActividad(_ctx: ServiceContext, id: string, evidenciaUrl?: string): Promise<ProgramaActividad | null> {
    const actividad = await this.actividadRepository.findOne({
      where: { id } as FindOptionsWhere<ProgramaActividad>,
    });
    if (!actividad) return null;

    actividad.fechaRealizada = new Date();
    if (evidenciaUrl) actividad.evidenciaUrl = evidenciaUrl;
    actividad.estado = 'completada';

    return this.actividadRepository.save(actividad);
  }

  // ========== Documentos STPS ==========
  async createDocumento(ctx: ServiceContext, dto: CreateDocumentoDto): Promise<DocumentoStps> {
    const documento = this.documentoRepository.create({
      tenantId: ctx.tenantId,
      tipo: dto.tipo,
      folio: dto.folio,
      fraccionamientoId: dto.fraccionamientoId,
      employeeId: dto.employeeId,
      fechaEmision: dto.fechaEmision,
      fechaVencimiento: dto.fechaVencimiento,
      datosDocumento: dto.datosDocumento,
      documentoUrl: dto.documentoUrl,
      createdById: ctx.userId,
    });

    return this.documentoRepository.save(documento);
  }

  async findDocumentos(ctx: ServiceContext, fraccionamientoId?: string, tipo?: TipoDocumentoStps): Promise<DocumentoStps[]> {
    const queryBuilder = this.documentoRepository
      .createQueryBuilder('d')
      .leftJoinAndSelect('d.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('d.employee', 'employee')
      .where('d.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

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

    return queryBuilder.orderBy('d.created_at', 'DESC').getMany();
  }

  async findDocumentoById(ctx: ServiceContext, id: string): Promise<DocumentoStps | null> {
    return this.documentoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<DocumentoStps>,
      relations: ['fraccionamiento', 'employee', 'createdBy'],
    });
  }

  async marcarDocumentoFirmado(ctx: ServiceContext, id: string): Promise<DocumentoStps | null> {
    const documento = await this.documentoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<DocumentoStps>,
    });
    if (!documento) return null;

    documento.firmado = true;
    return this.documentoRepository.save(documento);
  }

  // ========== Auditorías ==========
  async findAuditorias(
    ctx: ServiceContext,
    filters: AuditoriaFilters = {},
    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.dateFrom) {
      queryBuilder.andWhere('a.fecha_programada >= :dateFrom', { dateFrom: filters.dateFrom });
    }

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

    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 findAuditoriaById(ctx: ServiceContext, id: string): Promise<Auditoria | null> {
    return this.auditoriaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<Auditoria>,
      relations: ['fraccionamiento'],
    });
  }

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

    return this.auditoriaRepository.save(auditoria);
  }

  async completarAuditoria(
    ctx: ServiceContext,
    id: string,
    resultado: ResultadoAuditoria,
    noConformidades: number,
    observaciones?: string,
    informeUrl?: string
  ): Promise<Auditoria | null> {
    const auditoria = await this.auditoriaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<Auditoria>,
    });
    if (!auditoria) return null;

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

    return this.auditoriaRepository.save(auditoria);
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<StpsStats> {
    // Total normas (shared catalog)
    const totalNormas = await this.normaRepository.count({
      where: { activo: true } as FindOptionsWhere<NormaStps>,
    });

    // Cumplimiento por estado
    const cumplQuery = this.cumplimientoRepository
      .createQueryBuilder('c')
      .select('c.estado', 'estado')
      .addSelect('COUNT(*)', 'count')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId });
    if (fraccionamientoId) {
      cumplQuery.andWhere('c.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }
    const cumplimientoPorEstado = await cumplQuery.groupBy('c.estado').getRawMany();

    // Porcentaje cumplimiento
    const totalCumplimientos = cumplimientoPorEstado.reduce((sum, c) => sum + parseInt(c.count, 10), 0);
    const cumple = cumplimientoPorEstado.find((c) => c.estado === 'cumple');
    const porcentajeCumplimiento = totalCumplimientos > 0
      ? Math.round((parseInt(cumple?.count || '0', 10) / totalCumplimientos) * 100)
      : 0;

    // Comisiones activas
    const comisionQuery = this.comisionRepository
      .createQueryBuilder('c')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('c.estado = :estado', { estado: 'activa' });
    if (fraccionamientoId) {
      comisionQuery.andWhere('c.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }
    const comisionesActivas = await comisionQuery.getCount();

    // Recorridos pendientes
    const recorridosPendientes = await this.recorridoRepository
      .createQueryBuilder('r')
      .innerJoin('r.comision', 'c')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('r.estado IN (:...estados)', { estados: ['programado', 'pendiente'] })
      .getCount();

    // Actividades pendientes
    const actividadesPendientes = await this.actividadRepository
      .createQueryBuilder('a')
      .innerJoin('a.programa', 'p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.estado IN (:...estados)', { estados: ['pendiente', 'en_progreso'] })
      .getCount();

    // Auditorías programadas (sin resultado)
    const auditoriasProgramadas = await this.auditoriaRepository
      .createQueryBuilder('a')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.resultado IS NULL')
      .getCount();

    return {
      totalNormas,
      cumplimientoPorEstado: cumplimientoPorEstado.map((c) => ({ estado: c.estado, count: parseInt(c.count, 10) })),
      porcentajeCumplimiento,
      comisionesActivas,
      recorridosPendientes,
      actividadesPendientes,
      auditoriasProgramadas,
    };
  }
}
