/**
 * ReporteProgramadoService - Servicio para gestión de reportes HSE programados
 *
 * RF-MAA017-008: Gestión de reportes programados para envío automático
 * con soporte para múltiples frecuencias y formatos.
 *
 * @module HSE
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { ReporteProgramado, TipoReporteHse, FormatoReporte } from '../entities/reporte-programado.entity';
import { ServiceContext, PaginatedResult } from '../../../shared/services/base.service';

export interface ReporteProgramadoFilters {
  tipoReporte?: TipoReporteHse;
  formato?: FormatoReporte;
  activo?: boolean;
  search?: string;
}

export interface CreateReporteProgramadoDto {
  nombre: string;
  tipoReporte: TipoReporteHse;
  indicadores?: string[];
  fraccionamientos?: string[];
  destinatarios?: string[];
  diaEnvio?: number;
  horaEnvio?: string;
  formato?: FormatoReporte;
}

export interface UpdateReporteProgramadoDto {
  nombre?: string;
  tipoReporte?: TipoReporteHse;
  indicadores?: string[];
  fraccionamientos?: string[];
  destinatarios?: string[];
  diaEnvio?: number;
  horaEnvio?: string;
  formato?: FormatoReporte;
  activo?: boolean;
}

export interface ReporteConProximoEnvio extends ReporteProgramado {
  proximoEnvio: Date | null;
  diasParaEnvio: number | null;
}

export interface ReporteProgramadoStats {
  totalReportes: number;
  reportesActivos: number;
  porTipo: { tipo: string; count: number }[];
  porFormato: { formato: string; count: number }[];
  proximosEnvios: { reporteId: string; nombre: string; proximoEnvio: Date }[];
  enviosUltimaSemana: number;
}

export interface HistorialEnvio {
  reporteId: string;
  reporteNombre: string;
  fechaEnvio: Date;
  exitoso: boolean;
  destinatarios: string[];
  error?: string;
}

export class ReporteProgramadoService {
  constructor(
    private readonly reporteRepository: Repository<ReporteProgramado>
  ) {}

  // ========== CRUD de Reportes Programados ==========
  async findAll(
    ctx: ServiceContext,
    filters: ReporteProgramadoFilters = {},
    page: number = 1,
    limit: number = 20
  ): Promise<PaginatedResult<ReporteProgramado>> {
    const skip = (page - 1) * limit;

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

    if (filters.tipoReporte) {
      queryBuilder.andWhere('r.tipo_reporte = :tipoReporte', { tipoReporte: filters.tipoReporte });
    }

    if (filters.formato) {
      queryBuilder.andWhere('r.formato = :formato', { formato: filters.formato });
    }

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

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

    queryBuilder
      .orderBy('r.nombre', 'ASC')
      .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<ReporteProgramado | null> {
    return this.reporteRepository.findOne({
      where: { id, tenantId: ctx.tenantId } as FindOptionsWhere<ReporteProgramado>,
    });
  }

  async findActivos(ctx: ServiceContext): Promise<ReporteProgramado[]> {
    return this.reporteRepository.find({
      where: { tenantId: ctx.tenantId, activo: true } as FindOptionsWhere<ReporteProgramado>,
      order: { nombre: 'ASC' },
    });
  }

  async findByTipo(ctx: ServiceContext, tipoReporte: TipoReporteHse): Promise<ReporteProgramado[]> {
    return this.reporteRepository.find({
      where: {
        tenantId: ctx.tenantId,
        tipoReporte,
        activo: true,
      } as FindOptionsWhere<ReporteProgramado>,
      order: { nombre: 'ASC' },
    });
  }

  async create(ctx: ServiceContext, dto: CreateReporteProgramadoDto): Promise<ReporteProgramado> {
    // Validate destinatarios are email addresses
    if (dto.destinatarios) {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      for (const email of dto.destinatarios) {
        if (!emailRegex.test(email)) {
          throw new Error(`Destinatario invalido: ${email}`);
        }
      }
    }

    // Set default diaEnvio based on tipo
    let diaEnvio = dto.diaEnvio;
    if (diaEnvio === undefined) {
      switch (dto.tipoReporte) {
        case 'semanal':
          diaEnvio = 1; // Monday
          break;
        case 'mensual':
          diaEnvio = 1; // First day
          break;
        case 'trimestral':
          diaEnvio = 1; // First day
          break;
        case 'anual':
          diaEnvio = 15; // Mid-January
          break;
      }
    }

    const reporte = this.reporteRepository.create({
      tenantId: ctx.tenantId,
      nombre: dto.nombre,
      tipoReporte: dto.tipoReporte,
      indicadores: dto.indicadores,
      fraccionamientos: dto.fraccionamientos,
      destinatarios: dto.destinatarios,
      diaEnvio,
      horaEnvio: dto.horaEnvio || '08:00',
      formato: dto.formato || 'pdf',
      activo: true,
    });

    return this.reporteRepository.save(reporte);
  }

  async update(
    ctx: ServiceContext,
    id: string,
    dto: UpdateReporteProgramadoDto
  ): Promise<ReporteProgramado | null> {
    const reporte = await this.findById(ctx, id);
    if (!reporte) return null;

    // Validate destinatarios if provided
    if (dto.destinatarios) {
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      for (const email of dto.destinatarios) {
        if (!emailRegex.test(email)) {
          throw new Error(`Destinatario invalido: ${email}`);
        }
      }
    }

    if (dto.nombre !== undefined) reporte.nombre = dto.nombre;
    if (dto.tipoReporte !== undefined) reporte.tipoReporte = dto.tipoReporte;
    if (dto.indicadores !== undefined) reporte.indicadores = dto.indicadores;
    if (dto.fraccionamientos !== undefined) reporte.fraccionamientos = dto.fraccionamientos;
    if (dto.destinatarios !== undefined) reporte.destinatarios = dto.destinatarios;
    if (dto.diaEnvio !== undefined) reporte.diaEnvio = dto.diaEnvio;
    if (dto.horaEnvio !== undefined) reporte.horaEnvio = dto.horaEnvio;
    if (dto.formato !== undefined) reporte.formato = dto.formato;
    if (dto.activo !== undefined) reporte.activo = dto.activo;

    return this.reporteRepository.save(reporte);
  }

  async activate(ctx: ServiceContext, id: string): Promise<ReporteProgramado | null> {
    return this.update(ctx, id, { activo: true });
  }

  async deactivate(ctx: ServiceContext, id: string): Promise<ReporteProgramado | null> {
    return this.update(ctx, id, { activo: false });
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.reporteRepository.delete({
      id,
      tenantId: ctx.tenantId,
    } as FindOptionsWhere<ReporteProgramado>);

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

  // ========== Cálculo de Próximo Envío ==========
  private calcularProximoEnvio(reporte: ReporteProgramado): Date | null {
    if (!reporte.activo) return null;

    const now = new Date();
    const proximoEnvio = new Date();

    // Parse hora
    const [hora, minuto] = (reporte.horaEnvio || '08:00').split(':').map(Number);
    proximoEnvio.setHours(hora, minuto, 0, 0);

    switch (reporte.tipoReporte) {
      case 'semanal': {
        // diaEnvio: 0 = Sunday, 1 = Monday, etc.
        const dia = reporte.diaEnvio || 1;
        const diasHastaEnvio = (dia - now.getDay() + 7) % 7 || 7;
        proximoEnvio.setDate(now.getDate() + diasHastaEnvio);
        if (proximoEnvio <= now) {
          proximoEnvio.setDate(proximoEnvio.getDate() + 7);
        }
        break;
      }
      case 'mensual': {
        // diaEnvio: day of month (1-28)
        const dia = Math.min(reporte.diaEnvio || 1, 28);
        proximoEnvio.setDate(dia);
        if (proximoEnvio <= now) {
          proximoEnvio.setMonth(proximoEnvio.getMonth() + 1);
        }
        break;
      }
      case 'trimestral': {
        // diaEnvio: day of month
        const dia = Math.min(reporte.diaEnvio || 1, 28);
        const currentQuarter = Math.floor(now.getMonth() / 3);
        const nextQuarterMonth = (currentQuarter + 1) * 3;
        proximoEnvio.setMonth(nextQuarterMonth % 12);
        proximoEnvio.setDate(dia);
        if (nextQuarterMonth >= 12) {
          proximoEnvio.setFullYear(proximoEnvio.getFullYear() + 1);
        }
        if (proximoEnvio <= now) {
          proximoEnvio.setMonth(proximoEnvio.getMonth() + 3);
        }
        break;
      }
      case 'anual': {
        // diaEnvio: day of January
        const dia = Math.min(reporte.diaEnvio || 15, 28);
        proximoEnvio.setMonth(0, dia); // January
        if (proximoEnvio <= now) {
          proximoEnvio.setFullYear(proximoEnvio.getFullYear() + 1);
        }
        break;
      }
    }

    return proximoEnvio;
  }

  async findWithProximoEnvio(
    ctx: ServiceContext,
    filters: ReporteProgramadoFilters = {}
  ): Promise<ReporteConProximoEnvio[]> {
    const result = await this.findAll(ctx, filters, 1, 1000);
    const now = new Date();

    return result.data.map((reporte) => {
      const proximoEnvio = this.calcularProximoEnvio(reporte);
      const diasParaEnvio = proximoEnvio
        ? Math.ceil((proximoEnvio.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
        : null;

      return {
        ...reporte,
        proximoEnvio,
        diasParaEnvio,
      };
    });
  }

  // ========== Reportes Pendientes de Envío ==========
  async findPendientesEnvio(ctx: ServiceContext): Promise<ReporteProgramado[]> {
    const reportes = await this.findActivos(ctx);
    const pendientes: ReporteProgramado[] = [];
    const now = new Date();

    for (const reporte of reportes) {
      const proximoEnvio = this.calcularProximoEnvio(reporte);
      if (proximoEnvio && proximoEnvio <= now) {
        pendientes.push(reporte);
      }
    }

    return pendientes;
  }

  // ========== Marcar Envío ==========
  async marcarEnviado(ctx: ServiceContext, id: string): Promise<ReporteProgramado | null> {
    const reporte = await this.findById(ctx, id);
    if (!reporte) return null;

    reporte.ultimoEnvio = new Date();
    return this.reporteRepository.save(reporte);
  }

  // ========== Gestión de Destinatarios ==========
  async addDestinatario(ctx: ServiceContext, id: string, email: string): Promise<ReporteProgramado | null> {
    const reporte = await this.findById(ctx, id);
    if (!reporte) return null;

    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
      throw new Error('Email invalido');
    }

    const destinatarios = reporte.destinatarios || [];
    if (!destinatarios.includes(email)) {
      destinatarios.push(email);
      reporte.destinatarios = destinatarios;
      return this.reporteRepository.save(reporte);
    }

    return reporte;
  }

  async removeDestinatario(ctx: ServiceContext, id: string, email: string): Promise<ReporteProgramado | null> {
    const reporte = await this.findById(ctx, id);
    if (!reporte) return null;

    const destinatarios = reporte.destinatarios || [];
    const index = destinatarios.indexOf(email);
    if (index > -1) {
      destinatarios.splice(index, 1);
      reporte.destinatarios = destinatarios;
      return this.reporteRepository.save(reporte);
    }

    return reporte;
  }

  // ========== Gestión de Indicadores ==========
  async addIndicador(ctx: ServiceContext, id: string, indicadorId: string): Promise<ReporteProgramado | null> {
    const reporte = await this.findById(ctx, id);
    if (!reporte) return null;

    const indicadores = reporte.indicadores || [];
    if (!indicadores.includes(indicadorId)) {
      indicadores.push(indicadorId);
      reporte.indicadores = indicadores;
      return this.reporteRepository.save(reporte);
    }

    return reporte;
  }

  async removeIndicador(ctx: ServiceContext, id: string, indicadorId: string): Promise<ReporteProgramado | null> {
    const reporte = await this.findById(ctx, id);
    if (!reporte) return null;

    const indicadores = reporte.indicadores || [];
    const index = indicadores.indexOf(indicadorId);
    if (index > -1) {
      indicadores.splice(index, 1);
      reporte.indicadores = indicadores;
      return this.reporteRepository.save(reporte);
    }

    return reporte;
  }

  // ========== Gestión de Fraccionamientos ==========
  async addFraccionamiento(ctx: ServiceContext, id: string, fraccionamientoId: string): Promise<ReporteProgramado | null> {
    const reporte = await this.findById(ctx, id);
    if (!reporte) return null;

    const fraccionamientos = reporte.fraccionamientos || [];
    if (!fraccionamientos.includes(fraccionamientoId)) {
      fraccionamientos.push(fraccionamientoId);
      reporte.fraccionamientos = fraccionamientos;
      return this.reporteRepository.save(reporte);
    }

    return reporte;
  }

  async removeFraccionamiento(ctx: ServiceContext, id: string, fraccionamientoId: string): Promise<ReporteProgramado | null> {
    const reporte = await this.findById(ctx, id);
    if (!reporte) return null;

    const fraccionamientos = reporte.fraccionamientos || [];
    const index = fraccionamientos.indexOf(fraccionamientoId);
    if (index > -1) {
      fraccionamientos.splice(index, 1);
      reporte.fraccionamientos = fraccionamientos;
      return this.reporteRepository.save(reporte);
    }

    return reporte;
  }

  // ========== Duplicar Reporte ==========
  async duplicar(ctx: ServiceContext, id: string, nuevoNombre: string): Promise<ReporteProgramado | null> {
    const original = await this.findById(ctx, id);
    if (!original) return null;

    const duplicado = this.reporteRepository.create({
      tenantId: ctx.tenantId,
      nombre: nuevoNombre,
      tipoReporte: original.tipoReporte,
      indicadores: original.indicadores ? [...original.indicadores] : undefined,
      fraccionamientos: original.fraccionamientos ? [...original.fraccionamientos] : undefined,
      destinatarios: original.destinatarios ? [...original.destinatarios] : undefined,
      diaEnvio: original.diaEnvio,
      horaEnvio: original.horaEnvio,
      formato: original.formato,
      activo: false, // Start inactive
    });

    return this.reporteRepository.save(duplicado);
  }

  // ========== Estadísticas ==========
  async getStats(ctx: ServiceContext): Promise<ReporteProgramadoStats> {
    const totalReportes = await this.reporteRepository.count({
      where: { tenantId: ctx.tenantId } as FindOptionsWhere<ReporteProgramado>,
    });

    const reportesActivos = await this.reporteRepository.count({
      where: { tenantId: ctx.tenantId, activo: true } as FindOptionsWhere<ReporteProgramado>,
    });

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

    const porTipo = porTipoRaw.map((t) => ({
      tipo: t.tipo,
      count: parseInt(t.count, 10),
    }));

    // Por formato
    const porFormatoRaw = await this.reporteRepository
      .createQueryBuilder('r')
      .select('r.formato', 'formato')
      .addSelect('COUNT(*)', 'count')
      .where('r.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .groupBy('r.formato')
      .getRawMany();

    const porFormato = porFormatoRaw.map((f) => ({
      formato: f.formato,
      count: parseInt(f.count, 10),
    }));

    // Próximos envíos
    const reportesConProximo = await this.findWithProximoEnvio(ctx, { activo: true });
    const proximosEnvios = reportesConProximo
      .filter((r) => r.proximoEnvio !== null)
      .sort((a, b) => (a.proximoEnvio!.getTime() - b.proximoEnvio!.getTime()))
      .slice(0, 5)
      .map((r) => ({
        reporteId: r.id,
        nombre: r.nombre,
        proximoEnvio: r.proximoEnvio!,
      }));

    // Envíos última semana
    const semanaAtras = new Date();
    semanaAtras.setDate(semanaAtras.getDate() - 7);

    const enviosUltimaSemana = await this.reporteRepository
      .createQueryBuilder('r')
      .where('r.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('r.ultimo_envio >= :semanaAtras', { semanaAtras })
      .getCount();

    return {
      totalReportes,
      reportesActivos,
      porTipo,
      porFormato,
      proximosEnvios,
      enviosUltimaSemana,
    };
  }

  // ========== Validación de Configuración ==========
  async validarConfiguracion(ctx: ServiceContext, id: string): Promise<{ valido: boolean; errores: string[] }> {
    const errores: string[] = [];
    const reporte = await this.findById(ctx, id);

    if (!reporte) {
      return { valido: false, errores: ['Reporte no encontrado'] };
    }

    if (!reporte.destinatarios || reporte.destinatarios.length === 0) {
      errores.push('El reporte no tiene destinatarios configurados');
    }

    if (!reporte.indicadores || reporte.indicadores.length === 0) {
      errores.push('El reporte no tiene indicadores seleccionados');
    }

    if (!reporte.fraccionamientos || reporte.fraccionamientos.length === 0) {
      errores.push('El reporte no tiene fraccionamientos seleccionados');
    }

    if (!reporte.diaEnvio) {
      errores.push('El reporte no tiene dia de envio configurado');
    }

    return {
      valido: errores.length === 0,
      errores,
    };
  }
}
