/**
 * SesionCapacitacionService - Servicio para sesiones y asistencia de capacitación
 *
 * RF-MAA017-002: Control de Capacitaciones - Sesiones y Asistencia
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { CapacitacionSesion, EstadoSesion } from '../entities/capacitacion-sesion.entity';
import { CapacitacionAsistente } from '../entities/capacitacion-asistente.entity';
import { CapacitacionMatriz } from '../entities/capacitacion-matriz.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface CreateSesionDto {
  capacitacionId: string;
  fraccionamientoId?: string;
  instructorId?: string;
  fechaProgramada: Date;
  horaInicio: string;
  horaFin: string;
  lugar?: string;
  cupoMaximo?: number;
  observaciones?: string;
}

export interface UpdateSesionDto {
  instructorId?: string;
  fechaProgramada?: Date;
  horaInicio?: string;
  horaFin?: string;
  lugar?: string;
  cupoMaximo?: number;
  observaciones?: string;
}

export interface SesionFilters {
  capacitacionId?: string;
  fraccionamientoId?: string;
  instructorId?: string;
  estado?: EstadoSesion;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface RegistrarAsistenciaDto {
  sesionId: string;
  employeeId: string;
  asistio: boolean;
  horaEntrada?: string;
  horaSalida?: string;
  observaciones?: string;
}

export interface EvaluarAsistenteDto {
  calificacion: number;
  aprobado: boolean;
  observaciones?: string;
}

export interface SesionStats {
  totalSesiones: number;
  sesionesProgramadas: number;
  sesionesCompletadas: number;
  sesionesCanceladas: number;
  totalAsistentes: number;
  asistentesAprobados: number;
}

export class SesionCapacitacionService {
  constructor(
    private readonly sesionRepository: Repository<CapacitacionSesion>,
    private readonly asistenteRepository: Repository<CapacitacionAsistente>,
    private readonly matrizRepository: Repository<CapacitacionMatriz>
  ) {}

  // ========== Sesiones ==========
  async findSesiones(
    ctx: ServiceContext,
    filters: SesionFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<CapacitacionSesion>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.sesionRepository
      .createQueryBuilder('sesion')
      .leftJoinAndSelect('sesion.capacitacion', 'capacitacion')
      .leftJoinAndSelect('sesion.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('sesion.instructor', 'instructor')
      .where('sesion.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.capacitacionId) {
      queryBuilder.andWhere('sesion.capacitacion_id = :capacitacionId', {
        capacitacionId: filters.capacitacionId,
      });
    }

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

    if (filters.instructorId) {
      queryBuilder.andWhere('sesion.instructor_id = :instructorId', {
        instructorId: filters.instructorId,
      });
    }

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

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

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

    queryBuilder
      .orderBy('sesion.fecha_programada', 'DESC')
      .addOrderBy('sesion.hora_inicio', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  async findSesionById(ctx: ServiceContext, id: string): Promise<CapacitacionSesion | null> {
    return this.sesionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<CapacitacionSesion>,
      relations: ['capacitacion', 'fraccionamiento', 'instructor', 'asistentes', 'asistentes.employee'],
    });
  }

  async createSesion(ctx: ServiceContext, dto: CreateSesionDto): Promise<CapacitacionSesion> {
    const sesion = this.sesionRepository.create({
      tenantId: ctx.tenantId,
      capacitacionId: dto.capacitacionId,
      fraccionamientoId: dto.fraccionamientoId,
      instructorId: dto.instructorId,
      fechaProgramada: dto.fechaProgramada,
      horaInicio: dto.horaInicio,
      horaFin: dto.horaFin,
      lugar: dto.lugar,
      cupoMaximo: dto.cupoMaximo,
      observaciones: dto.observaciones,
      estado: 'programada',
    });

    return this.sesionRepository.save(sesion);
  }

  async updateSesion(ctx: ServiceContext, id: string, dto: UpdateSesionDto): Promise<CapacitacionSesion | null> {
    const sesion = await this.findSesionById(ctx, id);
    if (!sesion) return null;

    if (sesion.estado !== 'programada') {
      throw new Error('Solo se pueden modificar sesiones programadas');
    }

    Object.assign(sesion, dto);
    return this.sesionRepository.save(sesion);
  }

  async iniciarSesion(ctx: ServiceContext, id: string): Promise<CapacitacionSesion | null> {
    const sesion = await this.findSesionById(ctx, id);
    if (!sesion) return null;

    if (sesion.estado !== 'programada') {
      throw new Error('Solo se pueden iniciar sesiones programadas');
    }

    sesion.estado = 'en_curso';
    return this.sesionRepository.save(sesion);
  }

  async completarSesion(ctx: ServiceContext, id: string): Promise<CapacitacionSesion | null> {
    const sesion = await this.findSesionById(ctx, id);
    if (!sesion) return null;

    if (sesion.estado !== 'en_curso') {
      throw new Error('Solo se pueden completar sesiones en curso');
    }

    sesion.estado = 'completada';
    return this.sesionRepository.save(sesion);
  }

  async cancelarSesion(ctx: ServiceContext, id: string, observaciones?: string): Promise<CapacitacionSesion | null> {
    const sesion = await this.findSesionById(ctx, id);
    if (!sesion) return null;

    if (sesion.estado === 'completada') {
      throw new Error('No se pueden cancelar sesiones completadas');
    }

    sesion.estado = 'cancelada';
    if (observaciones) {
      sesion.observaciones = sesion.observaciones
        ? `${sesion.observaciones}\nCancelación: ${observaciones}`
        : `Cancelación: ${observaciones}`;
    }
    return this.sesionRepository.save(sesion);
  }

  // ========== Asistentes ==========
  async findAsistentesBySesion(sesionId: string): Promise<CapacitacionAsistente[]> {
    return this.asistenteRepository.find({
      where: { sesionId } as FindOptionsWhere<CapacitacionAsistente>,
      relations: ['employee'],
      order: { createdAt: 'ASC' },
    });
  }

  async inscribirAsistente(sesionId: string, employeeId: string): Promise<CapacitacionAsistente> {
    const existing = await this.asistenteRepository.findOne({
      where: { sesionId, employeeId } as FindOptionsWhere<CapacitacionAsistente>,
    });

    if (existing) {
      throw new Error('El empleado ya está inscrito en esta sesión');
    }

    const asistente = this.asistenteRepository.create({
      sesionId,
      employeeId,
      asistio: false,
    });

    return this.asistenteRepository.save(asistente);
  }

  async registrarAsistencia(dto: RegistrarAsistenciaDto): Promise<CapacitacionAsistente> {
    let asistente = await this.asistenteRepository.findOne({
      where: {
        sesionId: dto.sesionId,
        employeeId: dto.employeeId,
      } as FindOptionsWhere<CapacitacionAsistente>,
    });

    if (!asistente) {
      asistente = this.asistenteRepository.create({
        sesionId: dto.sesionId,
        employeeId: dto.employeeId,
      });
    }

    asistente.asistio = dto.asistio;
    if (dto.horaEntrada) asistente.horaEntrada = dto.horaEntrada;
    if (dto.horaSalida) asistente.horaSalida = dto.horaSalida;
    if (dto.observaciones) asistente.observaciones = dto.observaciones;

    return this.asistenteRepository.save(asistente);
  }

  async evaluarAsistente(asistenteId: string, dto: EvaluarAsistenteDto): Promise<CapacitacionAsistente | null> {
    const asistente = await this.asistenteRepository.findOne({
      where: { id: asistenteId } as FindOptionsWhere<CapacitacionAsistente>,
    });

    if (!asistente) return null;

    if (!asistente.asistio) {
      throw new Error('No se puede evaluar a un asistente que no asistió');
    }

    asistente.calificacion = dto.calificacion;
    asistente.aprobado = dto.aprobado;
    if (dto.observaciones) {
      asistente.observaciones = dto.observaciones;
    }

    return this.asistenteRepository.save(asistente);
  }

  async eliminarAsistente(asistenteId: string): Promise<boolean> {
    const result = await this.asistenteRepository.delete(asistenteId);
    return (result.affected || 0) > 0;
  }

  // ========== Matriz de Capacitación ==========
  async findMatrizByPuesto(ctx: ServiceContext, puestoId: string): Promise<CapacitacionMatriz[]> {
    return this.matrizRepository.find({
      where: { tenantId: ctx.tenantId, puestoId } as FindOptionsWhere<CapacitacionMatriz>,
      relations: ['capacitacion'],
      order: { esObligatoria: 'DESC' },
    });
  }

  async addCapacitacionToMatriz(
    ctx: ServiceContext,
    puestoId: string,
    capacitacionId: string,
    esObligatoria: boolean = true,
    plazoDias: number = 30
  ): Promise<CapacitacionMatriz> {
    const existing = await this.matrizRepository.findOne({
      where: { tenantId: ctx.tenantId, puestoId, capacitacionId } as FindOptionsWhere<CapacitacionMatriz>,
    });

    if (existing) {
      throw new Error('La capacitación ya está en la matriz para este puesto');
    }

    const matriz = this.matrizRepository.create({
      tenantId: ctx.tenantId,
      puestoId,
      capacitacionId,
      esObligatoria,
      plazoDias,
    });

    return this.matrizRepository.save(matriz);
  }

  async removeCapacitacionFromMatriz(ctx: ServiceContext, matrizId: string): Promise<boolean> {
    const matriz = await this.matrizRepository.findOne({
      where: { id: matrizId, tenantId: ctx.tenantId } as FindOptionsWhere<CapacitacionMatriz>,
    });

    if (!matriz) return false;

    await this.matrizRepository.remove(matriz);
    return true;
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<SesionStats> {
    const baseQuery = this.sesionRepository
      .createQueryBuilder('s')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    const totalSesiones = await baseQuery.getCount();

    const sesionesProgramadas = await this.sesionRepository
      .createQueryBuilder('s')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('s.estado = :estado', { estado: 'programada' })
      .andWhere(fraccionamientoId ? 's.fraccionamiento_id = :fraccionamientoId' : '1=1', { fraccionamientoId })
      .getCount();

    const sesionesCompletadas = await this.sesionRepository
      .createQueryBuilder('s')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('s.estado = :estado', { estado: 'completada' })
      .andWhere(fraccionamientoId ? 's.fraccionamiento_id = :fraccionamientoId' : '1=1', { fraccionamientoId })
      .getCount();

    const sesionesCanceladas = await this.sesionRepository
      .createQueryBuilder('s')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('s.estado = :estado', { estado: 'cancelada' })
      .andWhere(fraccionamientoId ? 's.fraccionamiento_id = :fraccionamientoId' : '1=1', { fraccionamientoId })
      .getCount();

    const asistentesQuery = this.asistenteRepository
      .createQueryBuilder('a')
      .innerJoin('a.sesion', 's')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (fraccionamientoId) {
      asistentesQuery.andWhere('s.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }

    const totalAsistentes = await asistentesQuery.andWhere('a.asistio = true').getCount();

    const asistentesAprobados = await this.asistenteRepository
      .createQueryBuilder('a')
      .innerJoin('a.sesion', 's')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.aprobado = true')
      .andWhere(fraccionamientoId ? 's.fraccionamiento_id = :fraccionamientoId' : '1=1', { fraccionamientoId })
      .getCount();

    return {
      totalSesiones,
      sesionesProgramadas,
      sesionesCompletadas,
      sesionesCanceladas,
      totalAsistentes,
      asistentesAprobados,
    };
  }

  // ========== Sesiones Próximas ==========
  async getSesionesProximas(ctx: ServiceContext, dias: number = 7): Promise<CapacitacionSesion[]> {
    const fechaLimite = new Date();
    fechaLimite.setDate(fechaLimite.getDate() + dias);

    return this.sesionRepository
      .createQueryBuilder('s')
      .leftJoinAndSelect('s.capacitacion', 'capacitacion')
      .leftJoinAndSelect('s.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('s.instructor', 'instructor')
      .where('s.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('s.estado = :estado', { estado: 'programada' })
      .andWhere('s.fecha_programada >= :hoy', { hoy: new Date() })
      .andWhere('s.fecha_programada <= :fechaLimite', { fechaLimite })
      .orderBy('s.fecha_programada', 'ASC')
      .addOrderBy('s.hora_inicio', 'ASC')
      .getMany();
  }
}
