<?php
/**
 * SLACalculator v2 - Calcula fechas de vencimiento en HORAS HÁBILES
 * 
 * IMPORTANTE: Trabaja internamente con MINUTOS ENTEROS para evitar
 * bugs de precisión de punto flotante que causaban bucles infinitos
 * y fechas como año 10141284.
 */
class SLACalculator {
    
    /**
     * Calcula la fecha de vencimiento sumando horas hábiles desde $desde (o ahora)
     */
    public static function calcularVencimiento(int $horasHabiles, ?string $desde = null): string {
        $config = self::getConfig();
        $tz = new \DateTimeZone($config['zona_horaria']);
        $fecha = $desde ? new \DateTime($desde, $tz) : new \DateTime('now', $tz);
        
        $hi = $config['hora_inicio'];
        $mi = $config['min_inicio'];
        $hf = $config['hora_fin'];
        $mf = $config['min_fin'];
        $diasLab = $config['dias_laborales'];
        
        // Minutos de jornada por día
        $minutosJornada = ($hf * 60 + $mf) - ($hi * 60 + $mi);
        if ($minutosJornada <= 0) {
            // Config inválida: fallback a horas calendario
            return $fecha->modify("+{$horasHabiles} hours")->format('Y-m-d H:i:s');
        }
        
        // Trabajar en MINUTOS ENTEROS para evitar errores de punto flotante
        $minutosRestantes = $horasHabiles * 60;
        
        // Si estamos fuera de horario hábil, mover al inicio del siguiente día hábil
        if (!self::esHoraHabil($fecha, $hi, $mi, $hf, $mf, $diasLab)) {
            self::moverASiguienteDiaHabil($fecha, $hi, $mi, $diasLab);
        }
        
        // Protección contra bucle infinito
        $maxIteraciones = 500;
        $iter = 0;
        
        while ($minutosRestantes > 0 && $iter < $maxIteraciones) {
            $iter++;
            
            // Minutos disponibles hoy (desde hora actual hasta fin de jornada)
            $minutosActualDia = (int)$fecha->format('H') * 60 + (int)$fecha->format('i');
            $minutosFinJornada = $hf * 60 + $mf;
            $minutosDisponiblesHoy = $minutosFinJornada - $minutosActualDia;
            
            if ($minutosDisponiblesHoy <= 0) {
                // Jornada terminada, ir al siguiente día hábil
                self::moverASiguienteDiaHabil($fecha, $hi, $mi, $diasLab);
                continue;
            }
            
            if ($minutosRestantes <= $minutosDisponiblesHoy) {
                // Se resuelve hoy: sumar los minutos exactos
                $fecha->modify("+{$minutosRestantes} minutes");
                $minutosRestantes = 0;
            } else {
                // Consumir todo el día y pasar al siguiente
                $minutosRestantes -= $minutosDisponiblesHoy;
                self::moverASiguienteDiaHabil($fecha, $hi, $mi, $diasLab);
            }
        }
        
        return $fecha->format('Y-m-d H:i:s');
    }
    
    /**
     * Verifica si una fecha/hora está dentro del horario hábil
     */
    private static function esHoraHabil(\DateTime $fecha, int $hi, int $mi, int $hf, int $mf, array $diasLab): bool {
        $diaSemana = (int)$fecha->format('N'); // 1=Lun, 7=Dom
        if (!in_array($diaSemana, $diasLab)) return false;
        
        $minutosActual = (int)$fecha->format('H') * 60 + (int)$fecha->format('i');
        $minutosInicio = $hi * 60 + $mi;
        $minutosFin = $hf * 60 + $mf;
        
        return $minutosActual >= $minutosInicio && $minutosActual < $minutosFin;
    }
    
    /**
     * Mueve $fecha al inicio del siguiente día hábil (modifica in-place)
     */
    private static function moverASiguienteDiaHabil(\DateTime &$fecha, int $hi, int $mi, array $diasLab): void {
        // Siempre avanzar al menos al día siguiente
        $fecha->modify('+1 day');
        $fecha->setTime($hi, $mi, 0);
        
        // Avanzar hasta encontrar un día laboral (max 7 intentos)
        $intentos = 0;
        while (!in_array((int)$fecha->format('N'), $diasLab) && $intentos < 7) {
            $fecha->modify('+1 day');
            $intentos++;
        }
    }
    
    /**
     * Carga la configuración de horario desde BD
     */
    private static function getConfig(): array {
        $defaults = [
            'hora_inicio' => 8, 'min_inicio' => 0,
            'hora_fin' => 18, 'min_fin' => 0,
            'dias_laborales' => [1,2,3,4,5],
            'zona_horaria' => 'America/Bogota'
        ];
        
        try {
            $db = getDB();
            $stmt = $db->prepare("SELECT clave, valor FROM configuracion WHERE grupo = 'sla'");
            $stmt->execute();
            $rows = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR);
            
            if (!empty($rows['horario_inicio'])) {
                $parts = explode(':', $rows['horario_inicio']);
                $defaults['hora_inicio'] = (int)($parts[0] ?? 8);
                $defaults['min_inicio'] = (int)($parts[1] ?? 0);
            }
            if (!empty($rows['horario_fin'])) {
                $parts = explode(':', $rows['horario_fin']);
                $defaults['hora_fin'] = (int)($parts[0] ?? 18);
                $defaults['min_fin'] = (int)($parts[1] ?? 0);
            }
            if (!empty($rows['dias_laborales'])) {
                $defaults['dias_laborales'] = array_map('intval', explode(',', $rows['dias_laborales']));
            }
            if (!empty($rows['zona_horaria'])) {
                $defaults['zona_horaria'] = $rows['zona_horaria'];
            }
        } catch (\Exception $e) {
            error_log("SLACalculator config error: " . $e->getMessage());
        }
        
        return $defaults;
    }
    
    /**
     * Obtiene las horas SLA para una prioridad dada.
     * Mapea 'critica' → busca 'sla_critica' y fallback a 'sla_urgente'
     */
    public static function getHorasSLA(string $prioridad, ?int $categoriaId = null): int {
        $defaults = ['baja' => 48, 'media' => 24, 'alta' => 8, 'critica' => 4, 'urgente' => 4];
        $horas = $defaults[$prioridad] ?? 24;
        
        try {
            $db = getDB();
            
            // Buscar por clave exacta: sla_{prioridad}
            $stmt = $db->prepare("SELECT valor FROM configuracion WHERE clave = ? AND grupo = 'sla'");
            $stmt->execute(["sla_$prioridad"]);
            $val = $stmt->fetchColumn();
            
            // Si prioridad='critica' y no encontró 'sla_critica', buscar 'sla_urgente'
            if (!$val && $prioridad === 'critica') {
                $stmt->execute(['sla_urgente']);
                $val = $stmt->fetchColumn();
            }
            
            if ($val) $horas = (int)$val;
            
            // Categoría: solo aplica si NO hay SLA configurado por prioridad
            // (la prioridad siempre tiene precedencia)
            if ($categoriaId && !$val) {
                $catStmt = $db->prepare("SELECT sla_horas FROM categorias WHERE id = ? AND sla_horas > 0");
                $catStmt->execute([$categoriaId]);
                $catSla = $catStmt->fetchColumn();
                if ($catSla && (int)$catSla > 0) {
                    $horas = (int)$catSla;
                }
            }
            
        } catch (\Exception $e) {
            error_log("SLACalculator getHorasSLA error: " . $e->getMessage());
        }
        
        return $horas;
    }
    
    /**
     * Texto legible del SLA para emails
     */
    public static function textoSLA(int $horas): string {
        if ($horas >= 24) {
            $dias = round($horas / 8, 1); // 8h = 1 día hábil
            return "{$horas} horas hábiles (~{$dias} días hábiles)";
        }
        return "{$horas} horas hábiles";
    }
}
