/**
 * IncidenteService - Servicio para gestión de incidentes HSE
 *
 * Gestión de incidentes de seguridad con workflow y relaciones.
 * Workflow: abierto -> en_investigacion -> cerrado
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import {
  Incidente,
  TipoIncidente,
  GravedadIncidente,
  EstadoIncidente,
} from '../entities/incidente.entity';
import { IncidenteInvolucrado, RolInvolucrado } from '../entities/incidente-involucrado.entity';
import { IncidenteAccion, EstadoAccion } from '../entities/incidente-accion.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface CreateIncidenteDto {
  fechaHora: Date;
  fraccionamientoId: string;
  ubicacionDescripcion?: string;
  tipo: TipoIncidente;
  gravedad: GravedadIncidente;
  descripcion: string;
  causaInmediata?: string;
  causaBasica?: string;
}

export interface UpdateIncidenteDto {
  ubicacionDescripcion?: string;
  tipo?: TipoIncidente;
  gravedad?: GravedadIncidente;
  descripcion?: string;
  causaInmediata?: string;
  causaBasica?: string;
}

export interface AddInvolucradoDto {
  employeeId: string;
  rol: RolInvolucrado;
  descripcionLesion?: string;
  parteCuerpo?: string;
  diasIncapacidad?: number;
}

export interface AddAccionDto {
  descripcion: string;
  tipo: string;
  responsableId?: string;
  fechaCompromiso: Date;
}

export interface UpdateAccionDto {
  descripcion?: string;
  responsableId?: string;
  fechaCompromiso?: Date;
  estado?: EstadoAccion;
  evidenciaUrl?: string;
  observaciones?: string;
}

export interface IncidenteFilters {
  fraccionamientoId?: string;
  tipo?: TipoIncidente;
  gravedad?: GravedadIncidente;
  estado?: EstadoIncidente;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface IncidenteStats {
  total: number;
  porTipo: { tipo: string; count: number }[];
  porGravedad: { gravedad: string; count: number }[];
  porEstado: { estado: string; count: number }[];
  diasSinAccidente: number;
}

export class IncidenteService {
  constructor(
    private readonly incidenteRepository: Repository<Incidente>,
    private readonly involucradoRepository: Repository<IncidenteInvolucrado>,
    private readonly accionRepository: Repository<IncidenteAccion>
  ) {}

  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 `INC-${year}${month}-${random}`;
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: IncidenteFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<Incidente>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.incidenteRepository
      .createQueryBuilder('incidente')
      .leftJoinAndSelect('incidente.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('incidente.createdBy', 'createdBy')
      .where('incidente.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

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

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

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

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

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

    queryBuilder
      .orderBy('incidente.fecha_hora', 'DESC')
      .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<Incidente | null> {
    return this.incidenteRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<Incidente>,
    });
  }

  async findWithDetails(ctx: ServiceContext, id: string): Promise<Incidente | null> {
    return this.incidenteRepository.findOne({
      where: {
        id,
        tenantId: ctx.tenantId,
      } as FindOptionsWhere<Incidente>,
      relations: [
        'fraccionamiento',
        'createdBy',
        'involucrados',
        'involucrados.employee',
        'acciones',
        'acciones.responsable',
      ],
    });
  }

  async create(ctx: ServiceContext, dto: CreateIncidenteDto): Promise<Incidente> {
    const incidente = this.incidenteRepository.create({
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
      folio: this.generateFolio(),
      fechaHora: dto.fechaHora,
      fraccionamientoId: dto.fraccionamientoId,
      ubicacionDescripcion: dto.ubicacionDescripcion,
      tipo: dto.tipo,
      gravedad: dto.gravedad,
      descripcion: dto.descripcion,
      causaInmediata: dto.causaInmediata,
      causaBasica: dto.causaBasica,
      estado: 'abierto',
    });

    return this.incidenteRepository.save(incidente);
  }

  async update(ctx: ServiceContext, id: string, dto: UpdateIncidenteDto): Promise<Incidente | null> {
    const existing = await this.findById(ctx, id);
    if (!existing) {
      return null;
    }

    if (existing.estado === 'cerrado') {
      throw new Error('Cannot update a closed incident');
    }

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

  async startInvestigation(ctx: ServiceContext, id: string): Promise<Incidente | null> {
    const existing = await this.findById(ctx, id);
    if (!existing) {
      return null;
    }

    if (existing.estado !== 'abierto') {
      throw new Error('Can only start investigation on open incidents');
    }

    existing.estado = 'en_investigacion';
    return this.incidenteRepository.save(existing);
  }

  async closeIncident(ctx: ServiceContext, id: string): Promise<Incidente | null> {
    const existing = await this.findWithDetails(ctx, id);
    if (!existing) {
      return null;
    }

    if (existing.estado === 'cerrado') {
      throw new Error('Incident is already closed');
    }

    // Check if all actions are completed or verified
    const pendingActions = existing.acciones?.filter(
      (a) => a.estado !== 'completada' && a.estado !== 'verificada'
    );

    if (pendingActions && pendingActions.length > 0) {
      throw new Error('Cannot close incident with pending actions');
    }

    existing.estado = 'cerrado';
    return this.incidenteRepository.save(existing);
  }

  async addInvolucrado(
    ctx: ServiceContext,
    incidenteId: string,
    dto: AddInvolucradoDto
  ): Promise<IncidenteInvolucrado> {
    const incidente = await this.findById(ctx, incidenteId);
    if (!incidente) {
      throw new Error('Incidente not found');
    }

    const involucrado = this.involucradoRepository.create({
      incidenteId,
      employeeId: dto.employeeId,
      rol: dto.rol,
      descripcionLesion: dto.descripcionLesion,
      parteCuerpo: dto.parteCuerpo,
      diasIncapacidad: dto.diasIncapacidad || 0,
    });

    return this.involucradoRepository.save(involucrado);
  }

  async removeInvolucrado(ctx: ServiceContext, incidenteId: string, involucradoId: string): Promise<boolean> {
    const incidente = await this.findById(ctx, incidenteId);
    if (!incidente) {
      return false;
    }

    const result = await this.involucradoRepository.delete({
      id: involucradoId,
      incidenteId,
    });

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

  async addAccion(ctx: ServiceContext, incidenteId: string, dto: AddAccionDto): Promise<IncidenteAccion> {
    const incidente = await this.findById(ctx, incidenteId);
    if (!incidente) {
      throw new Error('Incidente not found');
    }

    if (incidente.estado === 'cerrado') {
      throw new Error('Cannot add actions to a closed incident');
    }

    const accion = this.accionRepository.create({
      incidenteId,
      descripcion: dto.descripcion,
      tipo: dto.tipo,
      responsableId: dto.responsableId,
      fechaCompromiso: dto.fechaCompromiso,
      estado: 'pendiente',
    });

    return this.accionRepository.save(accion);
  }

  async updateAccion(
    ctx: ServiceContext,
    incidenteId: string,
    accionId: string,
    dto: UpdateAccionDto
  ): Promise<IncidenteAccion | null> {
    const incidente = await this.findById(ctx, incidenteId);
    if (!incidente) {
      return null;
    }

    const accion = await this.accionRepository.findOne({
      where: { id: accionId, incidenteId },
    });

    if (!accion) {
      return null;
    }

    // If completing the action, set the close date
    if (dto.estado === 'completada' && accion.estado !== 'completada') {
      dto.fechaCompromiso = dto.fechaCompromiso; // keep existing
      accion.fechaCierre = new Date();
    }

    const updated = this.accionRepository.merge(accion, dto);
    return this.accionRepository.save(updated);
  }

  async getStats(ctx: ServiceContext, fraccionamientoId?: string): Promise<IncidenteStats> {
    const queryBuilder = this.incidenteRepository
      .createQueryBuilder('incidente')
      .where('incidente.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    const total = await queryBuilder.getCount();

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

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

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

    // Calculate days since last accident
    const lastAccident = await this.incidenteRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        tipo: 'accidente',
      } as FindOptionsWhere<Incidente>,
      order: { fechaHora: 'DESC' },
    });

    let diasSinAccidente = 0;
    if (lastAccident) {
      const diff = Date.now() - new Date(lastAccident.fechaHora).getTime();
      diasSinAccidente = Math.floor(diff / (1000 * 60 * 60 * 24));
    }

    return {
      total,
      porTipo: porTipo.map((p) => ({ tipo: p.tipo, count: parseInt(p.count, 10) })),
      porGravedad: porGravedad.map((p) => ({ gravedad: p.gravedad, count: parseInt(p.count, 10) })),
      porEstado: porEstado.map((p) => ({ estado: p.estado, count: parseInt(p.count, 10) })),
      diasSinAccidente,
    };
  }
}
