/**
 * ProgramaSeguridadService - Servicio para programas de seguridad
 *
 * RF-MAA017-005: Gestion de programas anuales de seguridad y sus actividades
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { ProgramaSeguridad, EstadoPrograma } from '../entities/programa-seguridad.entity';
import { ProgramaActividad, TipoActividadPrograma, EstadoActividad } from '../entities/programa-actividad.entity';
import { ProgramaInspeccion, EstadoInspeccion } from '../entities/programa-inspeccion.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface ProgramaFilters {
  fraccionamientoId?: string;
  anio?: number;
  estado?: EstadoPrograma;
}

export interface ActividadFilters {
  programaId?: string;
  tipo?: TipoActividadPrograma;
  estado?: EstadoActividad;
  responsableId?: string;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface InspeccionProgramadaFilters {
  fraccionamientoId?: string;
  tipoInspeccionId?: string;
  inspectorId?: string;
  estado?: EstadoInspeccion;
  dateFrom?: Date;
  dateTo?: Date;
}

export interface CreateProgramaSeguridadDto {
  fraccionamientoId: string;
  anio: number;
  objetivoGeneral?: string;
  metas?: Record<string, unknown>;
  presupuesto?: number;
}

export interface UpdateProgramaSeguridadDto {
  objetivoGeneral?: string;
  metas?: Record<string, unknown>;
  presupuesto?: number;
}

export interface CreateActividadProgramaDto {
  programaId: string;
  actividad: string;
  tipo: TipoActividadPrograma;
  fechaProgramada: Date;
  responsableId?: string;
  recursos?: string;
}

export interface UpdateActividadDto {
  actividad?: string;
  tipo?: TipoActividadPrograma;
  fechaProgramada?: Date;
  responsableId?: string;
  recursos?: string;
  estado?: EstadoActividad;
  evidenciaUrl?: string;
}

export interface CreateInspeccionProgramadaDto {
  fraccionamientoId: string;
  tipoInspeccionId: string;
  inspectorId?: string;
  fechaProgramada: Date;
  horaProgramada?: string;
  zonaArea?: string;
}

export interface ProgramaStats {
  totalProgramas: number;
  programasActivos: number;
  actividadesPendientes: number;
  actividadesCompletadas: number;
  cumplimiento: number;
}

export interface CalendarioActividad {
  id: string;
  fecha: Date;
  titulo: string;
  tipo: TipoActividadPrograma | 'inspeccion';
  estado: string;
  responsable?: string;
}

export class ProgramaSeguridadService {
  constructor(
    private readonly programaRepository: Repository<ProgramaSeguridad>,
    private readonly actividadRepository: Repository<ProgramaActividad>,
    private readonly inspeccionProgramadaRepository: Repository<ProgramaInspeccion>
  ) {}

  // ========== Programas ==========

  /**
   * Lista programas con filtros
   */
  async findProgramas(
    ctx: ServiceContext,
    filters: ProgramaFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ProgramaSeguridad>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.programaRepository
      .createQueryBuilder('programa')
      .leftJoinAndSelect('programa.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('programa.aprobadoPor', 'aprobadoPor')
      .where('programa.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    if (filters.anio) {
      queryBuilder.andWhere('programa.anio = :anio', { anio: filters.anio });
    }

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

    queryBuilder
      .orderBy('programa.anio', 'DESC')
      .addOrderBy('programa.created_at', 'DESC')
      .skip(skip)
      .take(limit);

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

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

  /**
   * Obtiene programa por ID
   */
  async findProgramaById(ctx: ServiceContext, id: string): Promise<ProgramaSeguridad | null> {
    return this.programaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ProgramaSeguridad>,
      relations: ['fraccionamiento', 'aprobadoPor'],
    });
  }

  /**
   * Obtiene programa con todas sus actividades
   */
  async findProgramaWithActividades(ctx: ServiceContext, id: string): Promise<ProgramaSeguridad | null> {
    return this.programaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ProgramaSeguridad>,
      relations: ['fraccionamiento', 'aprobadoPor', 'actividades', 'actividades.responsable'],
    });
  }

  /**
   * Crea un nuevo programa
   */
  async createPrograma(ctx: ServiceContext, dto: CreateProgramaSeguridadDto): Promise<ProgramaSeguridad> {
    // Verificar si ya existe un programa para ese anio y fraccionamiento
    const existing = await this.programaRepository.findOne({
      where: {
        tenantId: ctx.tenantId,
        fraccionamientoId: dto.fraccionamientoId,
        anio: dto.anio,
      } as FindOptionsWhere<ProgramaSeguridad>,
    });

    if (existing) {
      throw new Error(`Ya existe un programa de seguridad para el anio ${dto.anio} en esta obra`);
    }

    const programa = this.programaRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      anio: dto.anio,
      objetivoGeneral: dto.objetivoGeneral,
      metas: dto.metas,
      presupuesto: dto.presupuesto,
      estado: 'borrador',
    });

    return this.programaRepository.save(programa);
  }

  /**
   * Actualiza un programa
   */
  async updatePrograma(
    ctx: ServiceContext,
    id: string,
    dto: UpdateProgramaSeguridadDto
  ): Promise<ProgramaSeguridad | null> {
    const programa = await this.findProgramaById(ctx, id);
    if (!programa) {
      return null;
    }

    if (programa.estado === 'finalizado') {
      throw new Error('No se puede modificar un programa finalizado');
    }

    const updated = this.programaRepository.merge(programa, dto);
    return this.programaRepository.save(updated);
  }

  /**
   * Activa un programa
   */
  async activarPrograma(ctx: ServiceContext, id: string): Promise<ProgramaSeguridad | null> {
    const programa = await this.findProgramaById(ctx, id);
    if (!programa) {
      return null;
    }

    if (programa.estado !== 'borrador') {
      throw new Error('Solo se pueden activar programas en borrador');
    }

    programa.estado = 'activo';
    programa.aprobadoPorId = ctx.userId || '';
    programa.fechaAprobacion = new Date();

    return this.programaRepository.save(programa);
  }

  /**
   * Finaliza un programa
   */
  async finalizarPrograma(ctx: ServiceContext, id: string): Promise<ProgramaSeguridad | null> {
    const programa = await this.findProgramaById(ctx, id);
    if (!programa) {
      return null;
    }

    if (programa.estado !== 'activo') {
      throw new Error('Solo se pueden finalizar programas activos');
    }

    programa.estado = 'finalizado';
    return this.programaRepository.save(programa);
  }

  // ========== Actividades ==========

  /**
   * Lista actividades con filtros
   */
  async findActividades(
    ctx: ServiceContext,
    filters: ActividadFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ProgramaActividad>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.actividadRepository
      .createQueryBuilder('actividad')
      .leftJoinAndSelect('actividad.programa', 'programa')
      .leftJoinAndSelect('actividad.responsable', 'responsable')
      .where('programa.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.programaId) {
      queryBuilder.andWhere('actividad.programa_id = :programaId', {
        programaId: filters.programaId,
      });
    }

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

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

    if (filters.responsableId) {
      queryBuilder.andWhere('actividad.responsable_id = :responsableId', {
        responsableId: filters.responsableId,
      });
    }

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

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

    queryBuilder
      .orderBy('actividad.fecha_programada', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  /**
   * Obtiene actividad por ID
   */
  async findActividadById(_ctx: ServiceContext, id: string): Promise<ProgramaActividad | null> {
    return this.actividadRepository.findOne({
      where: { id } as FindOptionsWhere<ProgramaActividad>,
      relations: ['programa', 'responsable'],
    });
  }

  /**
   * Crea una nueva actividad
   */
  async createActividad(ctx: ServiceContext, dto: CreateActividadProgramaDto): Promise<ProgramaActividad> {
    const programa = await this.findProgramaById(ctx, dto.programaId);
    if (!programa) {
      throw new Error('Programa no encontrado');
    }

    if (programa.estado === 'finalizado') {
      throw new Error('No se pueden agregar actividades a un programa finalizado');
    }

    const actividad = this.actividadRepository.create({
      programaId: dto.programaId,
      actividad: dto.actividad,
      tipo: dto.tipo,
      fechaProgramada: dto.fechaProgramada,
      responsableId: dto.responsableId,
      recursos: dto.recursos,
      estado: 'pendiente',
    });

    return this.actividadRepository.save(actividad);
  }

  /**
   * Actualiza una actividad
   */
  async updateActividad(
    ctx: ServiceContext,
    id: string,
    dto: UpdateActividadDto
  ): Promise<ProgramaActividad | null> {
    const actividad = await this.findActividadById(ctx, id);
    if (!actividad) {
      return null;
    }

    const updated = this.actividadRepository.merge(actividad, dto);

    // Si se completa, registrar fecha
    if (dto.estado === 'completada' && actividad.estado !== 'completada') {
      updated.fechaRealizada = new Date();
    }

    return this.actividadRepository.save(updated);
  }

  /**
   * Marca actividad como completada
   */
  async completarActividad(
    ctx: ServiceContext,
    id: string,
    evidenciaUrl?: string
  ): Promise<ProgramaActividad | null> {
    const actividad = await this.findActividadById(ctx, id);
    if (!actividad) {
      return null;
    }

    actividad.estado = 'completada';
    actividad.fechaRealizada = new Date();
    if (evidenciaUrl) {
      actividad.evidenciaUrl = evidenciaUrl;
    }

    return this.actividadRepository.save(actividad);
  }

  /**
   * Cancela una actividad
   */
  async cancelarActividad(ctx: ServiceContext, id: string): Promise<ProgramaActividad | null> {
    const actividad = await this.findActividadById(ctx, id);
    if (!actividad) {
      return null;
    }

    if (actividad.estado === 'completada') {
      throw new Error('No se puede cancelar una actividad completada');
    }

    actividad.estado = 'cancelada';
    return this.actividadRepository.save(actividad);
  }

  // ========== Inspecciones Programadas ==========

  /**
   * Lista inspecciones programadas
   */
  async findInspeccionesProgramadas(
    ctx: ServiceContext,
    filters: InspeccionProgramadaFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ProgramaInspeccion>> {
    const skip = (page - 1) * limit;

    const queryBuilder = this.inspeccionProgramadaRepository
      .createQueryBuilder('pi')
      .leftJoinAndSelect('pi.fraccionamiento', 'fraccionamiento')
      .leftJoinAndSelect('pi.tipoInspeccion', 'tipoInspeccion')
      .leftJoinAndSelect('pi.inspector', 'inspector')
      .where('pi.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    if (filters.tipoInspeccionId) {
      queryBuilder.andWhere('pi.tipo_inspeccion_id = :tipoInspeccionId', {
        tipoInspeccionId: filters.tipoInspeccionId,
      });
    }

    if (filters.inspectorId) {
      queryBuilder.andWhere('pi.inspector_id = :inspectorId', {
        inspectorId: filters.inspectorId,
      });
    }

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

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

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

    queryBuilder
      .orderBy('pi.fecha_programada', 'ASC')
      .skip(skip)
      .take(limit);

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

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

  /**
   * Crea una inspeccion programada
   */
  async createInspeccionProgramada(
    ctx: ServiceContext,
    dto: CreateInspeccionProgramadaDto
  ): Promise<ProgramaInspeccion> {
    const inspeccion = this.inspeccionProgramadaRepository.create({
      tenantId: ctx.tenantId,
      fraccionamientoId: dto.fraccionamientoId,
      tipoInspeccionId: dto.tipoInspeccionId,
      inspectorId: dto.inspectorId,
      fechaProgramada: dto.fechaProgramada,
      horaProgramada: dto.horaProgramada,
      zonaArea: dto.zonaArea,
      estado: 'programada',
    });

    return this.inspeccionProgramadaRepository.save(inspeccion);
  }

  /**
   * Actualiza estado de inspeccion programada
   */
  async updateInspeccionProgramadaEstado(
    ctx: ServiceContext,
    id: string,
    estado: EstadoInspeccion,
    motivoCancelacion?: string
  ): Promise<ProgramaInspeccion | null> {
    const inspeccion = await this.inspeccionProgramadaRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ProgramaInspeccion>,
    });

    if (!inspeccion) {
      return null;
    }

    inspeccion.estado = estado;
    if (estado === 'cancelada' && motivoCancelacion) {
      inspeccion.motivoCancelacion = motivoCancelacion;
    }

    return this.inspeccionProgramadaRepository.save(inspeccion);
  }

  // ========== Calendario y Estadisticas ==========

  /**
   * Obtiene calendario de actividades e inspecciones
   */
  async getCalendario(
    ctx: ServiceContext,
    fraccionamientoId: string,
    dateFrom: Date,
    dateTo: Date
  ): Promise<CalendarioActividad[]> {
    const calendario: CalendarioActividad[] = [];

    // Obtener actividades del programa
    const actividades = await this.actividadRepository
      .createQueryBuilder('a')
      .leftJoinAndSelect('a.programa', 'p')
      .leftJoinAndSelect('a.responsable', 'r')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId })
      .andWhere('a.fecha_programada >= :dateFrom', { dateFrom })
      .andWhere('a.fecha_programada <= :dateTo', { dateTo })
      .getMany();

    for (const act of actividades) {
      calendario.push({
        id: act.id,
        fecha: act.fechaProgramada,
        titulo: act.actividad,
        tipo: act.tipo,
        estado: act.estado,
        responsable: act.responsable?.id,
      });
    }

    // Obtener inspecciones programadas
    const inspecciones = await this.inspeccionProgramadaRepository
      .createQueryBuilder('i')
      .leftJoinAndSelect('i.tipoInspeccion', 't')
      .leftJoinAndSelect('i.inspector', 'ins')
      .where('i.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('i.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId })
      .andWhere('i.fecha_programada >= :dateFrom', { dateFrom })
      .andWhere('i.fecha_programada <= :dateTo', { dateTo })
      .getMany();

    for (const insp of inspecciones) {
      calendario.push({
        id: insp.id,
        fecha: insp.fechaProgramada,
        titulo: `Inspeccion: ${insp.tipoInspeccion?.nombre || 'General'}`,
        tipo: 'inspeccion',
        estado: insp.estado,
        responsable: insp.inspectorId,
      });
    }

    // Ordenar por fecha
    calendario.sort((a, b) => new Date(a.fecha).getTime() - new Date(b.fecha).getTime());

    return calendario;
  }

  /**
   * Obtiene estadisticas del programa
   */
  async getStats(ctx: ServiceContext, fraccionamientoId?: string, anio?: number): Promise<ProgramaStats> {
    const queryBuilder = this.programaRepository
      .createQueryBuilder('p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId });

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

    if (anio) {
      queryBuilder.andWhere('p.anio = :anio', { anio });
    }

    const totalProgramas = await queryBuilder.getCount();

    const programasActivos = await this.programaRepository
      .createQueryBuilder('p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.estado = :estado', { estado: 'activo' })
      .getCount();

    // Estadisticas de actividades
    const actividadesQuery = this.actividadRepository
      .createQueryBuilder('a')
      .leftJoin('a.programa', 'p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (fraccionamientoId) {
      actividadesQuery.andWhere('p.fraccionamiento_id = :fraccionamientoId', { fraccionamientoId });
    }

    if (anio) {
      actividadesQuery.andWhere('p.anio = :anio', { anio });
    }

    const totalActividades = await actividadesQuery.getCount();

    const actividadesCompletadas = await actividadesQuery
      .clone()
      .andWhere('a.estado = :estado', { estado: 'completada' })
      .getCount();

    const actividadesPendientes = await actividadesQuery
      .clone()
      .andWhere('a.estado IN (:...estados)', { estados: ['pendiente', 'en_progreso'] })
      .getCount();

    const cumplimiento = totalActividades > 0
      ? Math.round((actividadesCompletadas / totalActividades) * 100)
      : 0;

    return {
      totalProgramas,
      programasActivos,
      actividadesPendientes,
      actividadesCompletadas,
      cumplimiento,
    };
  }
}
