/**
 * EppService - Servicio para control de EPP
 *
 * RF-MAA017-004: Gestión de equipo de protección personal
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { EppCatalogo, CategoriaEpp } from '../entities/epp-catalogo.entity';
import { EppAsignacion, EstadoEpp } from '../entities/epp-asignacion.entity';
import { EppInspeccion, EstadoInspeccionEpp } from '../entities/epp-inspeccion.entity';
import { EppBaja, MotivoBajaEpp } from '../entities/epp-baja.entity';
import { EppMatrizPuesto } from '../entities/epp-matriz-puesto.entity';
import { EppInventario } from '../entities/epp-inventario.entity';
import { EppMovimiento, TipoMovimientoEpp } from '../entities/epp-movimiento.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface CreateEppCatalogoDto {
  codigo: string;
  nombre: string;
  descripcion?: string;
  categoria: CategoriaEpp;
  especificaciones?: string;
  vidaUtilDias: number;
  normaReferencia?: string;
  requiereCertificacion?: boolean;
  requiereInspeccionPeriodica?: boolean;
  frecuenciaInspeccionDias?: number;
  alertaDiasAntes?: number;
  imagenUrl?: string;
}

export interface CreateAsignacionDto {
  eppId: string;
  employeeId: string;
  fraccionamientoId?: string;
  fechaEntrega: Date;
  fechaVencimiento: Date;
  numeroSerie?: string;
  numeroLote?: string;
  capacitacionUso?: boolean;
  costoUnitario?: number;
}

export interface CreateInspeccionEppDto {
  asignacionId: string;
  inspectorId: string;
  estadoEpp: EstadoInspeccionEpp;
  observaciones?: string;
  requiereReemplazo?: boolean;
  fotoUrl?: string;
}

export interface CreateBajaDto {
  asignacionId: string;
  motivo: MotivoBajaEpp;
  descripcion?: string;
  descuentoAplicado?: boolean;
  montoDescuento?: number;
  autorizadoPorId?: string;
}

export interface EppFilters {
  categoria?: CategoriaEpp;
  activo?: boolean;
  search?: string;
}

export interface AsignacionFilters {
  employeeId?: string;
  eppId?: string;
  estado?: EstadoEpp;
  fraccionamientoId?: string;
  vencidos?: boolean;
}

export interface EppStats {
  totalCatalogo: number;
  totalAsignaciones: number;
  asignacionesActivas: number;
  proximasAVencer: number;
  bajasPorMotivo: { motivo: string; count: number }[];
}

export class EppService {
  constructor(
    private readonly catalogoRepository: Repository<EppCatalogo>,
    private readonly asignacionRepository: Repository<EppAsignacion>,
    private readonly inspeccionRepository: Repository<EppInspeccion>,
    private readonly bajaRepository: Repository<EppBaja>,
    private readonly matrizRepository: Repository<EppMatrizPuesto>,
    private readonly inventarioRepository: Repository<EppInventario>,
    private readonly movimientoRepository: Repository<EppMovimiento>
  ) {}

  // ========== Catálogo EPP ==========
  async findCatalogo(
    ctx: ServiceContext,
    filters: EppFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<EppCatalogo>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.catalogoRepository
      .createQueryBuilder('epp')
      .where('epp.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.categoria) {
      queryBuilder.andWhere('epp.categoria = :categoria', { categoria: filters.categoria });
    }

    if (filters.activo !== undefined) {
      queryBuilder.andWhere('epp.activo = :activo', { activo: filters.activo });
    }

    if (filters.search) {
      queryBuilder.andWhere(
        '(epp.codigo ILIKE :search OR epp.nombre ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    queryBuilder
      .orderBy('epp.nombre', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  async findCatalogoById(ctx: ServiceContext, id: string): Promise<EppCatalogo | null> {
    return this.catalogoRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<EppCatalogo>,
    });
  }

  async createCatalogo(ctx: ServiceContext, dto: CreateEppCatalogoDto): Promise<EppCatalogo> {
    const epp = this.catalogoRepository.create({
      tenantId: ctx.tenantId,
      codigo: dto.codigo,
      nombre: dto.nombre,
      descripcion: dto.descripcion,
      categoria: dto.categoria,
      especificaciones: dto.especificaciones,
      vidaUtilDias: dto.vidaUtilDias,
      normaReferencia: dto.normaReferencia,
      requiereCertificacion: dto.requiereCertificacion ?? false,
      requiereInspeccionPeriodica: dto.requiereInspeccionPeriodica ?? false,
      frecuenciaInspeccionDias: dto.frecuenciaInspeccionDias,
      alertaDiasAntes: dto.alertaDiasAntes ?? 15,
      imagenUrl: dto.imagenUrl,
      activo: true,
    });
    return this.catalogoRepository.save(epp);
  }

  // ========== Matriz por Puesto ==========
  async getMatrizByPuesto(ctx: ServiceContext, puestoId: string): Promise<EppMatrizPuesto[]> {
    return this.matrizRepository.find({
      where: { tenantId: ctx.tenantId, puestoId } as FindOptionsWhere<EppMatrizPuesto>,
      relations: ['epp'],
    });
  }

  async setMatrizPuesto(
    ctx: ServiceContext,
    puestoId: string,
    eppId: string,
    esObligatorio: boolean,
    actividadEspecifica?: string
  ): Promise<EppMatrizPuesto> {
    let matriz = await this.matrizRepository.findOne({
      where: { tenantId: ctx.tenantId, puestoId, eppId } as FindOptionsWhere<EppMatrizPuesto>,
    });

    if (matriz) {
      matriz.esObligatorio = esObligatorio;
      if (actividadEspecifica !== undefined) {
        matriz.actividadEspecifica = actividadEspecifica;
      }
    } else {
      matriz = this.matrizRepository.create({
        tenantId: ctx.tenantId,
        puestoId,
        eppId,
        esObligatorio,
        actividadEspecifica,
      });
    }

    return this.matrizRepository.save(matriz);
  }

  // ========== Asignaciones ==========
  async findAsignaciones(
    ctx: ServiceContext,
    filters: AsignacionFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<EppAsignacion>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.asignacionRepository
      .createQueryBuilder('asignacion')
      .leftJoinAndSelect('asignacion.epp', 'epp')
      .leftJoinAndSelect('asignacion.employee', 'employee')
      .where('asignacion.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.employeeId) {
      queryBuilder.andWhere('asignacion.employee_id = :employeeId', {
        employeeId: filters.employeeId,
      });
    }

    if (filters.eppId) {
      queryBuilder.andWhere('asignacion.epp_id = :eppId', {
        eppId: filters.eppId,
      });
    }

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

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

    if (filters.vencidos) {
      queryBuilder.andWhere('asignacion.fecha_vencimiento < :today', { today: new Date() });
      queryBuilder.andWhere('asignacion.estado = :activo', { activo: 'activo' });
    }

    queryBuilder
      .orderBy('asignacion.fecha_entrega', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  async createAsignacion(ctx: ServiceContext, dto: CreateAsignacionDto): Promise<EppAsignacion> {
    const asignacion = this.asignacionRepository.create({
      tenantId: ctx.tenantId,
      eppId: dto.eppId,
      employeeId: dto.employeeId,
      fraccionamientoId: dto.fraccionamientoId,
      fechaEntrega: dto.fechaEntrega,
      fechaVencimiento: dto.fechaVencimiento,
      numeroSerie: dto.numeroSerie,
      numeroLote: dto.numeroLote,
      capacitacionUso: dto.capacitacionUso ?? false,
      costoUnitario: dto.costoUnitario,
      estado: 'activo',
      createdById: ctx.userId,
    });

    return this.asignacionRepository.save(asignacion);
  }

  async findAsignacionById(ctx: ServiceContext, id: string): Promise<EppAsignacion | null> {
    return this.asignacionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<EppAsignacion>,
      relations: ['epp', 'employee', 'fraccionamiento'],
    });
  }

  async updateAsignacionEstado(ctx: ServiceContext, id: string, estado: EstadoEpp): Promise<EppAsignacion | null> {
    const asignacion = await this.asignacionRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<EppAsignacion>,
    });
    if (!asignacion) return null;

    asignacion.estado = estado;
    return this.asignacionRepository.save(asignacion);
  }

  // ========== Inspecciones ==========
  async createInspeccion(ctx: ServiceContext, dto: CreateInspeccionEppDto): Promise<EppInspeccion> {
    const asignacion = await this.findAsignacionById(ctx, dto.asignacionId);
    if (!asignacion) throw new Error('Asignación no encontrada');

    const inspeccion = this.inspeccionRepository.create({
      asignacionId: dto.asignacionId,
      inspectorId: dto.inspectorId,
      fechaInspeccion: new Date(),
      estadoEpp: dto.estadoEpp,
      observaciones: dto.observaciones,
      requiereReemplazo: dto.requiereReemplazo ?? false,
      fotoUrl: dto.fotoUrl,
    });

    const saved = await this.inspeccionRepository.save(inspeccion);

    // If requires replacement, update assignment status
    if (dto.requiereReemplazo || dto.estadoEpp === 'danado' || dto.estadoEpp === 'malo') {
      asignacion.estado = 'danado';
      await this.asignacionRepository.save(asignacion);
    }

    return saved;
  }

  async getInspeccionesByAsignacion(_ctx: ServiceContext, asignacionId: string): Promise<EppInspeccion[]> {
    return this.inspeccionRepository.find({
      where: { asignacionId } as FindOptionsWhere<EppInspeccion>,
      relations: ['inspector'],
      order: { fechaInspeccion: 'DESC' },
    });
  }

  // ========== Bajas ==========
  async createBaja(ctx: ServiceContext, dto: CreateBajaDto): Promise<EppBaja> {
    const asignacion = await this.findAsignacionById(ctx, dto.asignacionId);
    if (!asignacion) throw new Error('Asignación no encontrada');

    if (asignacion.estado === 'devuelto') {
      throw new Error('EPP ya fue dado de baja');
    }

    const baja = this.bajaRepository.create({
      asignacionId: dto.asignacionId,
      fechaBaja: new Date(),
      motivo: dto.motivo,
      descripcion: dto.descripcion,
      descuentoAplicado: dto.descuentoAplicado ?? false,
      montoDescuento: dto.montoDescuento,
      autorizadoPorId: dto.autorizadoPorId,
    });

    const saved = await this.bajaRepository.save(baja);

    // Update assignment status based on motivo
    const estadoMap: Record<MotivoBajaEpp, EstadoEpp> = {
      vencimiento: 'vencido',
      danado: 'danado',
      perdido: 'perdido',
      terminacion_laboral: 'devuelto',
    };
    asignacion.estado = estadoMap[dto.motivo];
    await this.asignacionRepository.save(asignacion);

    return saved;
  }

  // ========== Inventario ==========
  async getInventario(ctx: ServiceContext, almacenId?: string): Promise<EppInventario[]> {
    const where: FindOptionsWhere<EppInventario> = { tenantId: ctx.tenantId };
    if (almacenId) {
      where.almacenId = almacenId;
    }

    return this.inventarioRepository.find({
      where,
      relations: ['epp'],
    });
  }

  async registrarMovimiento(
    ctx: ServiceContext,
    eppId: string,
    tipo: TipoMovimientoEpp,
    cantidad: number,
    almacenOrigenId?: string,
    almacenDestinoId?: string,
    referencia?: string
  ): Promise<EppMovimiento> {
    const movimiento = this.movimientoRepository.create({
      tenantId: ctx.tenantId,
      eppId,
      tipo,
      cantidad,
      almacenOrigenId,
      almacenDestinoId,
      referencia,
      createdById: ctx.userId,
    });

    return this.movimientoRepository.save(movimiento);
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<EppStats> {
    const totalCatalogo = await this.catalogoRepository.count({
      where: { tenantId: ctx.tenantId, activo: true } as FindOptionsWhere<EppCatalogo>,
    });

    const asignacionWhere: FindOptionsWhere<EppAsignacion> = { tenantId: ctx.tenantId };
    if (fraccionamientoId) {
      asignacionWhere.fraccionamientoId = fraccionamientoId;
    }

    const totalAsignaciones = await this.asignacionRepository.count({ where: asignacionWhere });

    const asignacionesActivas = await this.asignacionRepository.count({
      where: { ...asignacionWhere, estado: 'activo' } as FindOptionsWhere<EppAsignacion>,
    });

    // Próximas a vencer (30 días)
    const fechaLimite = new Date();
    fechaLimite.setDate(fechaLimite.getDate() + 30);
    const proximasAVencer = await this.asignacionRepository
      .createQueryBuilder('a')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('a.estado = :estado', { estado: 'activo' })
      .andWhere('a.fecha_vencimiento <= :fechaLimite', { fechaLimite })
      .andWhere('a.fecha_vencimiento >= :today', { today: new Date() })
      .getCount();

    // Bajas por motivo
    const bajasPorMotivo = await this.bajaRepository
      .createQueryBuilder('b')
      .innerJoin('b.asignacion', 'a')
      .select('b.motivo', 'motivo')
      .addSelect('COUNT(*)', 'count')
      .where('a.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .groupBy('b.motivo')
      .getRawMany();

    return {
      totalCatalogo,
      totalAsignaciones,
      asignacionesActivas,
      proximasAVencer,
      bajasPorMotivo: bajasPorMotivo.map((b) => ({ motivo: b.motivo, count: parseInt(b.count, 10) })),
    };
  }
}
