/**
 * HallazgoService - Servicio para gestion de hallazgos de seguridad
 *
 * RF-MAA017-003: Gestion de hallazgos, acciones correctivas y seguimiento
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { Hallazgo, GravedadHallazgo, EstadoHallazgo } from '../entities/hallazgo.entity';
import { HallazgoEvidencia, TipoEvidencia } from '../entities/hallazgo-evidencia.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface HallazgoServiceFilters {
  fraccionamientoId?: string;
  inspeccionId?: string;
  gravedad?: GravedadHallazgo;
  estado?: EstadoHallazgo;
  responsableId?: string;
  vencidos?: boolean;
  tipo?: 'acto_inseguro' | 'condicion_insegura';
  dateFrom?: Date;
  dateTo?: Date;
}

export interface CreateHallazgoServiceDto {
  inspeccionId: string;
  evaluacionId?: string;
  gravedad: GravedadHallazgo;
  tipo: 'acto_inseguro' | 'condicion_insegura';
  descripcion: string;
  ubicacionDescripcion?: string;
  responsableCorreccionId?: string;
  fechaLimite: Date;
}

export interface UpdateHallazgoDto {
  gravedad?: GravedadHallazgo;
  descripcion?: string;
  ubicacionDescripcion?: string;
  responsableCorreccionId?: string;
  fechaLimite?: Date;
}

export interface AddEvidenciaDto {
  tipo: TipoEvidencia;
  archivoUrl: string;
  descripcion?: string;
}

export interface RegistrarCorreccionDto {
  descripcionCorreccion: string;
  evidenciaUrl?: string;
}

export interface HallazgoStats {
  total: number;
  porGravedad: { gravedad: string; count: number }[];
  porEstado: { estado: string; count: number }[];
  porTipo: { tipo: string; count: number }[];
  vencidos: number;
  proximosAVencer: number;
  tiempoPromedioCorreccion: number;
}

export class HallazgoService {
  constructor(
    private readonly hallazgoRepository: Repository<Hallazgo>,
    private readonly evidenciaRepository: Repository<HallazgoEvidencia>
  ) {}

  private generateFolio(): 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 `HAL-${year}${month}-${random}`;
  }

  /**
   * Lista hallazgos con filtros y paginacion
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: HallazgoServiceFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<Hallazgo>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.hallazgoRepository
      .createQueryBuilder('hallazgo')
      .leftJoinAndSelect('hallazgo.inspeccion', 'inspeccion')
      .leftJoinAndSelect('inspeccion.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('hallazgo.responsableCorreccion', 'responsable')
      .leftJoinAndSelect('hallazgo.verificador', 'verificador')
      .where('hallazgo.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    if (filters.inspeccionId) {
      queryBuilder.andWhere('hallazgo.inspeccion_id = :inspeccionId', {
        inspeccionId: filters.inspeccionId,
      });
    }

    if (filters.gravedad) {
      queryBuilder.andWhere('hallazgo.gravedad = :gravedad', { gravedad: filters.gravedad });
    }

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

    if (filters.responsableId) {
      queryBuilder.andWhere('hallazgo.responsable_correccion_id = :responsableId', {
        responsableId: filters.responsableId,
      });
    }

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

    if (filters.vencidos) {
      queryBuilder.andWhere('hallazgo.fecha_limite < :today', { today: new Date() });
      queryBuilder.andWhere('hallazgo.estado NOT IN (:...cerrados)', { cerrados: ['cerrado'] });
    }

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

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

    queryBuilder
      .orderBy('hallazgo.fecha_limite', 'ASC')
      .addOrderBy('hallazgo.gravedad', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  /**
   * Obtiene hallazgo por ID
   */
  async findById(ctx: ServiceContext, id: string): Promise<Hallazgo | null> {
    return this.hallazgoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<Hallazgo>,
      relations: ['inspeccion', 'responsableCorreccion', 'verificador'],
    });
  }

  /**
   * Obtiene hallazgo con todas las relaciones
   */
  async findWithDetails(ctx: ServiceContext, id: string): Promise<Hallazgo | null> {
    return this.hallazgoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<Hallazgo>,
      relations: [
        'inspeccion',
        'inspeccion.fraccionamiento',
        'inspeccion.tipoInspeccion',
        'evaluacion',
        'responsableCorreccion',
        'verificador',
        'evidencias',
        'evidencias.createdBy',
      ],
    });
  }

  /**
   * Crea un nuevo hallazgo
   */
  async create(ctx: ServiceContext, dto: CreateHallazgoServiceDto): Promise<Hallazgo> {
    const hallazgo = this.hallazgoRepository.create({
      tenantId: ctx.tenantId,
      folio: this.generateFolio(),
      inspeccionId: dto.inspeccionId,
      evaluacionId: dto.evaluacionId,
      gravedad: dto.gravedad,
      tipo: dto.tipo,
      descripcion: dto.descripcion,
      ubicacionDescripcion: dto.ubicacionDescripcion,
      responsableCorreccionId: dto.responsableCorreccionId,
      fechaLimite: dto.fechaLimite,
      estado: 'abierto',
    });

    return this.hallazgoRepository.save(hallazgo);
  }

  /**
   * Actualiza un hallazgo
   */
  async update(ctx: ServiceContext, id: string, dto: UpdateHallazgoDto): Promise<Hallazgo | null> {
    const existing = await this.findById(ctx, id);
    if (!existing) {
      return null;
    }

    if (existing.estado === 'cerrado') {
      throw new Error('No se puede modificar un hallazgo cerrado');
    }

    const updated = this.hallazgoRepository.merge(existing, dto);
    return this.hallazgoRepository.save(updated);
  }

  /**
   * Inicia la correccion de un hallazgo
   */
  async iniciarCorreccion(ctx: ServiceContext, id: string): Promise<Hallazgo | null> {
    const hallazgo = await this.findById(ctx, id);
    if (!hallazgo) {
      return null;
    }

    if (hallazgo.estado !== 'abierto' && hallazgo.estado !== 'reabierto') {
      throw new Error('El hallazgo no esta en estado abierto o reabierto');
    }

    hallazgo.estado = 'en_correccion';
    return this.hallazgoRepository.save(hallazgo);
  }

  /**
   * Registra la correccion de un hallazgo
   */
  async registrarCorreccion(
    ctx: ServiceContext,
    id: string,
    dto: RegistrarCorreccionDto
  ): Promise<Hallazgo | null> {
    const hallazgo = await this.findById(ctx, id);
    if (!hallazgo) {
      return null;
    }

    if (hallazgo.estado === 'cerrado') {
      throw new Error('El hallazgo ya esta cerrado');
    }

    hallazgo.estado = 'verificando';
    hallazgo.descripcionCorreccion = dto.descripcionCorreccion;
    hallazgo.fechaCorreccion = new Date();

    const saved = await this.hallazgoRepository.save(hallazgo);

    // Agregar evidencia de correccion si se proporciona
    if (dto.evidenciaUrl) {
      await this.addEvidencia(ctx, id, {
        tipo: 'correccion',
        archivoUrl: dto.evidenciaUrl,
        descripcion: 'Evidencia de correccion',
      });
    }

    return saved;
  }

  /**
   * Verifica un hallazgo (aprueba o rechaza la correccion)
   */
  async verificar(
    ctx: ServiceContext,
    id: string,
    aprobado: boolean,
    observaciones?: string
  ): Promise<Hallazgo | null> {
    const hallazgo = await this.findById(ctx, id);
    if (!hallazgo) {
      return null;
    }

    if (hallazgo.estado !== 'verificando') {
      throw new Error('El hallazgo no esta en estado de verificacion');
    }

    hallazgo.verificadorId = ctx.userId || '';
    hallazgo.fechaVerificacion = new Date();
    hallazgo.estado = aprobado ? 'cerrado' : 'reabierto';

    if (observaciones && !aprobado) {
      hallazgo.descripcionCorreccion = `${hallazgo.descripcionCorreccion}\n\n[RECHAZADO]: ${observaciones}`;
    }

    return this.hallazgoRepository.save(hallazgo);
  }

  /**
   * Reabre un hallazgo cerrado
   */
  async reabrir(ctx: ServiceContext, id: string, motivo: string): Promise<Hallazgo | null> {
    const hallazgo = await this.findById(ctx, id);
    if (!hallazgo) {
      return null;
    }

    if (hallazgo.estado !== 'cerrado') {
      throw new Error('Solo se pueden reabrir hallazgos cerrados');
    }

    hallazgo.estado = 'reabierto';
    hallazgo.descripcionCorreccion = `${hallazgo.descripcionCorreccion}\n\n[REABIERTO]: ${motivo}`;
    hallazgo.fechaCorreccion = undefined as unknown as Date;
    hallazgo.fechaVerificacion = undefined as unknown as Date;

    return this.hallazgoRepository.save(hallazgo);
  }

  /**
   * Agrega evidencia a un hallazgo
   */
  async addEvidencia(
    ctx: ServiceContext,
    hallazgoId: string,
    dto: AddEvidenciaDto
  ): Promise<HallazgoEvidencia> {
    const hallazgo = await this.findById(ctx, hallazgoId);
    if (!hallazgo) {
      throw new Error('Hallazgo no encontrado');
    }

    const evidencia = this.evidenciaRepository.create({
      hallazgoId,
      tipo: dto.tipo,
      archivoUrl: dto.archivoUrl,
      descripcion: dto.descripcion,
      createdById: ctx.userId,
    });

    return this.evidenciaRepository.save(evidencia);
  }

  /**
   * Obtiene evidencias de un hallazgo
   */
  async getEvidencias(ctx: ServiceContext, hallazgoId: string): Promise<HallazgoEvidencia[]> {
    const hallazgo = await this.findById(ctx, hallazgoId);
    if (!hallazgo) {
      return [];
    }

    return this.evidenciaRepository.find({
      where: { hallazgoId } as FindOptionsWhere<HallazgoEvidencia>,
      relations: ['createdBy'],
      order: { createdAt: 'DESC' },
    });
  }

  /**
   * Elimina evidencia
   */
  async removeEvidencia(ctx: ServiceContext, hallazgoId: string, evidenciaId: string): Promise<boolean> {
    const hallazgo = await this.findById(ctx, hallazgoId);
    if (!hallazgo) {
      return false;
    }

    const result = await this.evidenciaRepository.delete({
      id: evidenciaId,
      hallazgoId,
    });

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

  /**
   * Obtiene hallazgos vencidos
   */
  async findVencidos(ctx: ServiceContext, fraccionamientoId?: string): Promise<Hallazgo[]> {
    const queryBuilder = this.hallazgoRepository
      .createQueryBuilder('hallazgo')
      .leftJoinAndSelect('hallazgo.inspeccion', 'inspeccion')
      .leftJoinAndSelect('hallazgo.responsableCorreccion', 'responsable')
      .where('hallazgo.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('hallazgo.fecha_limite < :today', { today: new Date() })
      .andWhere('hallazgo.estado NOT IN (:...cerrados)', { cerrados: ['cerrado'] });

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

    return queryBuilder
      .orderBy('hallazgo.fecha_limite', 'ASC')
      .getMany();
  }

  /**
   * Obtiene hallazgos proximos a vencer (7 dias)
   */
  async findProximosAVencer(
    ctx: ServiceContext,
    dias: number = 7,
    fraccionamientoId?: string
  ): Promise<Hallazgo[]> {
    const fechaLimite = new Date();
    fechaLimite.setDate(fechaLimite.getDate() + dias);

    const queryBuilder = this.hallazgoRepository
      .createQueryBuilder('hallazgo')
      .leftJoinAndSelect('hallazgo.inspeccion', 'inspeccion')
      .leftJoinAndSelect('hallazgo.responsableCorreccion', 'responsable')
      .where('hallazgo.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('hallazgo.fecha_limite >= :today', { today: new Date() })
      .andWhere('hallazgo.fecha_limite <= :fechaLimite', { fechaLimite })
      .andWhere('hallazgo.estado NOT IN (:...cerrados)', { cerrados: ['cerrado'] });

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

    return queryBuilder
      .orderBy('hallazgo.fecha_limite', 'ASC')
      .getMany();
  }

  /**
   * Obtiene estadisticas de hallazgos
   */
  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<HallazgoStats> {
    const baseQuery = this.hallazgoRepository
      .createQueryBuilder('h')
      .leftJoin('h.inspeccion', 'i')
      .where('h.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    const total = await baseQuery.getCount();

    // Por gravedad
    const porGravedad = await this.hallazgoRepository
      .createQueryBuilder('h')
      .select('h.gravedad', 'gravedad')
      .addSelect('COUNT(*)', 'count')
      .where('h.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .groupBy('h.gravedad')
      .getRawMany();

    // Por estado
    const porEstado = await this.hallazgoRepository
      .createQueryBuilder('h')
      .select('h.estado', 'estado')
      .addSelect('COUNT(*)', 'count')
      .where('h.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .groupBy('h.estado')
      .getRawMany();

    // Por tipo
    const porTipo = await this.hallazgoRepository
      .createQueryBuilder('h')
      .select('h.tipo', 'tipo')
      .addSelect('COUNT(*)', 'count')
      .where('h.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .groupBy('h.tipo')
      .getRawMany();

    // Vencidos
    const vencidos = await this.hallazgoRepository
      .createQueryBuilder('h')
      .where('h.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('h.fecha_limite < :today', { today: new Date() })
      .andWhere('h.estado NOT IN (:...cerrados)', { cerrados: ['cerrado'] })
      .getCount();

    // Proximos a vencer (7 dias)
    const fechaLimite = new Date();
    fechaLimite.setDate(fechaLimite.getDate() + 7);
    const proximosAVencer = await this.hallazgoRepository
      .createQueryBuilder('h')
      .where('h.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('h.fecha_limite >= :today', { today: new Date() })
      .andWhere('h.fecha_limite <= :fechaLimite', { fechaLimite })
      .andWhere('h.estado NOT IN (:...cerrados)', { cerrados: ['cerrado'] })
      .getCount();

    // Tiempo promedio de correccion
    const tiemposCorreccion = await this.hallazgoRepository
      .createQueryBuilder('h')
      .select('AVG(EXTRACT(EPOCH FROM (h.fecha_correccion - h.created_at)) / 86400)', 'avgDias')
      .where('h.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('h.fecha_correccion IS NOT NULL')
      .getRawOne();

    return {
      total,
      porGravedad: porGravedad.map((p) => ({ gravedad: p.gravedad, count: parseInt(p.count, 10) })),
      porEstado: porEstado.map((p) => ({ estado: p.estado, count: parseInt(p.count, 10) })),
      porTipo: porTipo.map((p) => ({ tipo: p.tipo, count: parseInt(p.count, 10) })),
      vencidos,
      proximosAVencer,
      tiempoPromedioCorreccion: tiemposCorreccion?.avgDias ? parseFloat(tiemposCorreccion.avgDias) : 0,
    };
  }
}
