/**
 * CumplimientoService - Servicio para cumplimiento normativo por obra
 *
 * RF-MAA017-005: Cumplimiento STPS - Verificación de cumplimiento
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { CumplimientoObra, EstadoCumplimiento } from '../entities/cumplimiento-obra.entity';
import { NormaStps } from '../entities/norma-stps.entity';
import { NormaRequisito } from '../entities/norma-requisito.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

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

export interface UpdateCumplimientoDto {
  estado?: EstadoCumplimiento;
  evidenciaUrl?: string;
  observaciones?: string;
  fechaCompromiso?: Date;
  evaluadorId?: string;
}

export interface CumplimientoObraFilters {
  fraccionamientoId?: string;
  normaId?: string;
  requisitoId?: string;
  estado?: EstadoCumplimiento;
  evaluadorId?: string;
  fechaDesde?: Date;
  fechaHasta?: Date;
}

export interface CumplimientoResumen {
  normaId: string;
  normaCodigo: string;
  normaNombre: string;
  totalRequisitos: number;
  cumple: number;
  parcial: number;
  noCumple: number;
  noAplica: number;
  porcentajeCumplimiento: number;
}

export interface CumplimientoStats {
  totalEvaluaciones: number;
  cumple: number;
  parcial: number;
  noCumple: number;
  noAplica: number;
  porcentajeCumplimiento: number;
  pendientesCompromiso: number;
}

export class CumplimientoService {
  constructor(
    private readonly cumplimientoRepository: Repository<CumplimientoObra>,
    private readonly normaRepository: Repository<NormaStps>,
    private readonly requisitoRepository: Repository<NormaRequisito>
  ) {}

  // ========== Normas ==========
  async findNormas(soloAplicaConstruccion: boolean = true): Promise<NormaStps[]> {
    const where: FindOptionsWhere<NormaStps> = { activo: true };
    if (soloAplicaConstruccion) {
      where.aplicaConstruccion = true;
    }

    return this.normaRepository.find({
      where,
      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 ==========
  async findCumplimientos(
    ctx: ServiceContext,
    filters: CumplimientoObraFilters = {},
    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.requisitoId) {
      queryBuilder.andWhere('c.requisito_id = :requisitoId', { requisitoId: filters.requisitoId });
    }

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

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

    if (filters.fechaDesde) {
      queryBuilder.andWhere('c.fecha_evaluacion >= :fechaDesde', { fechaDesde: filters.fechaDesde });
    }

    if (filters.fechaHasta) {
      queryBuilder.andWhere('c.fecha_evaluacion <= :fechaHasta', { fechaHasta: filters.fechaHasta });
    }

    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 findById(ctx: ServiceContext, id: string): Promise<CumplimientoObra | null> {
    return this.cumplimientoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<CumplimientoObra>,
      relations: ['fraccionamiento', 'norma', 'requisito', 'evaluador'],
    });
  }

  async create(ctx: ServiceContext, dto: CreateCumplimientoObraDto): 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 update(ctx: ServiceContext, id: string, dto: UpdateCumplimientoDto): Promise<CumplimientoObra | null> {
    const cumplimiento = await this.findById(ctx, id);
    if (!cumplimiento) return null;

    if (dto.estado) cumplimiento.estado = dto.estado;
    if (dto.evidenciaUrl !== undefined) cumplimiento.evidenciaUrl = dto.evidenciaUrl;
    if (dto.observaciones !== undefined) cumplimiento.observaciones = dto.observaciones;
    if (dto.fechaCompromiso !== undefined) cumplimiento.fechaCompromiso = dto.fechaCompromiso;
    if (dto.evaluadorId !== undefined) cumplimiento.evaluadorId = dto.evaluadorId;

    cumplimiento.fechaEvaluacion = new Date();

    return this.cumplimientoRepository.save(cumplimiento);
  }

  async updateEstado(
    ctx: ServiceContext,
    id: string,
    estado: EstadoCumplimiento,
    evidenciaUrl?: string,
    observaciones?: string
  ): Promise<CumplimientoObra | null> {
    const cumplimiento = await this.findById(ctx, id);
    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);
  }

  // ========== Evaluación Masiva ==========
  async evaluarNormaCompleta(
    ctx: ServiceContext,
    fraccionamientoId: string,
    normaId: string,
    evaluadorId: string
  ): Promise<CumplimientoObra[]> {
    const requisitos = await this.findRequisitosByNorma(normaId);
    const cumplimientos: CumplimientoObra[] = [];

    for (const requisito of requisitos) {
      const existing = await this.cumplimientoRepository.findOne({
        where: {
          tenantId: ctx.tenantId,
          fraccionamientoId,
          normaId,
          requisitoId: requisito.id,
        } as FindOptionsWhere<CumplimientoObra>,
      });

      if (!existing) {
        const cumplimiento = await this.create(ctx, {
          fraccionamientoId,
          normaId,
          requisitoId: requisito.id,
          evaluadorId,
          fechaEvaluacion: new Date(),
          estado: 'no_cumple',
        });
        cumplimientos.push(cumplimiento);
      }
    }

    return cumplimientos;
  }

  // ========== Resumen por Norma ==========
  async getResumenPorNorma(ctx: ServiceContext, fraccionamientoId: string): Promise<CumplimientoResumen[]> {
    const normas = await this.findNormas(true);
    const resumenes: CumplimientoResumen[] = [];

    for (const norma of normas) {
      const cumplimientos = await this.cumplimientoRepository.find({
        where: {
          tenantId: ctx.tenantId,
          fraccionamientoId,
          normaId: norma.id,
        } as FindOptionsWhere<CumplimientoObra>,
      });

      const cumple = cumplimientos.filter((c) => c.estado === 'cumple').length;
      const parcial = cumplimientos.filter((c) => c.estado === 'parcial').length;
      const noCumple = cumplimientos.filter((c) => c.estado === 'no_cumple').length;
      const noAplica = cumplimientos.filter((c) => c.estado === 'no_aplica').length;
      const totalRequisitos = cumplimientos.length;

      const evaluables = totalRequisitos - noAplica;
      const porcentajeCumplimiento = evaluables > 0
        ? Math.round(((cumple + parcial * 0.5) / evaluables) * 100)
        : 100;

      resumenes.push({
        normaId: norma.id,
        normaCodigo: norma.codigo,
        normaNombre: norma.nombre,
        totalRequisitos,
        cumple,
        parcial,
        noCumple,
        noAplica,
        porcentajeCumplimiento,
      });
    }

    return resumenes;
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<CumplimientoStats> {
    const queryBuilder = this.cumplimientoRepository
      .createQueryBuilder('c')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    const cumplimientos = await queryBuilder.getMany();

    const cumple = cumplimientos.filter((c) => c.estado === 'cumple').length;
    const parcial = cumplimientos.filter((c) => c.estado === 'parcial').length;
    const noCumple = cumplimientos.filter((c) => c.estado === 'no_cumple').length;
    const noAplica = cumplimientos.filter((c) => c.estado === 'no_aplica').length;
    const totalEvaluaciones = cumplimientos.length;

    const evaluables = totalEvaluaciones - noAplica;
    const porcentajeCumplimiento = evaluables > 0
      ? Math.round(((cumple + parcial * 0.5) / evaluables) * 100)
      : 100;

    const today = new Date();
    const pendientesCompromiso = cumplimientos.filter(
      (c) => c.estado !== 'cumple' && c.fechaCompromiso && c.fechaCompromiso < today
    ).length;

    return {
      totalEvaluaciones,
      cumple,
      parcial,
      noCumple,
      noAplica,
      porcentajeCumplimiento,
      pendientesCompromiso,
    };
  }

  // ========== Compromisos Pendientes ==========
  async getCompromisosPendientes(ctx: ServiceContext, fraccionamientoId?: string): Promise<CumplimientoObra[]> {
    const today = new Date();

    const queryBuilder = this.cumplimientoRepository
      .createQueryBuilder('c')
      .leftJoinAndSelect('c.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('c.norma', 'norma')
      .leftJoinAndSelect('c.requisito', 'requisito')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('c.estado IN (:...estados)', { estados: ['no_cumple', 'parcial'] })
      .andWhere('c.fecha_compromiso IS NOT NULL')
      .andWhere('c.fecha_compromiso < :today', { today });

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

    return queryBuilder.orderBy('c.fecha_compromiso', 'ASC').getMany();
  }

  // ========== No Cumplimientos ==========
  async getNoCumplimientos(ctx: ServiceContext, fraccionamientoId?: string): Promise<CumplimientoObra[]> {
    const queryBuilder = this.cumplimientoRepository
      .createQueryBuilder('c')
      .leftJoinAndSelect('c.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('c.norma', 'norma')
      .leftJoinAndSelect('c.requisito', 'requisito')
      .where('c.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('c.estado = :estado', { estado: 'no_cumple' });

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

    return queryBuilder
      .orderBy('norma.codigo', 'ASC')
      .addOrderBy('requisito.numero', 'ASC')
      .getMany();
  }
}
