<?php
/**
 * motor_fap.php — Motor de Facilidades de Pago (FAP) · RND 102500000019
 *
 * Reglas (RND 102500000019):
 *  - Pago inicial (Art.8) y garantía (Arts.15-17) según el TOTAL consolidado.
 *  - Cuota (Art.9): base = saldo/N, saldo = deuda − pago inicial − garantía;
 *    cada cuota se ACTUALIZA a su fecha de pago con MV + interés Art.47
 *    (Art.7.I.a / Art.9.II). => cuota_k = base · DA(fecha_k)/DA(solicitud),
 *    donde DA(f) = deuda actualizada a la fecha f (motor_siat.php).
 *  - Cuota mínima 200 UFV; máx. 30 meses; cuotas vencen el último día hábil
 *    del mes (1ª: mes siguiente a la notificación).
 *  - UFV de cuotas futuras: proyectada por tendencia (configurable: flat|trend).
 *
 * Validado contra el caso real FAP 252534005904 (Jean Paul Malky).
 */
require_once __DIR__ . '/motor_siat.php';
require_once __DIR__ . '/vencimientos.php';

class MotorFAP {
    const UFV_API = 'https://nitro.com.de/ufv/api.php';
    public $motor;
    private $ufvCache = [];
    private $proy = null; // [lastDate, lastUFV, dailyGrowth]

    public function __construct() { $this->motor = new MotorDeudaSIN(); }

    /** UFV de una fecha SOLO desde ufv_api (sin fallback BCB, para no bloquear). */
    private function apiUFV($fecha) {
        $ch = curl_init(self::UFV_API.'?action=get_rate&date='.urlencode($fecha));
        curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_SSL_VERIFYPEER=>false,
            CURLOPT_TIMEOUT=>8, CURLOPT_CONNECTTIMEOUT=>4]);
        $r = curl_exec($ch); curl_close($ch);
        $d = json_decode($r, true);
        if (is_array($d) && isset($d['error']) && $d['error']===false && isset($d['rate'])) {
            $v = round(floatval($d['rate']),5); if ($v>1 && $v<10) return $v;
        }
        return null;
    }

    /** %s de pago inicial y garantía efectivo/valores según total consolidado. */
    public static function porcentajes($total) {
        // RND 102600000020 (08/06/2026): pago inicial 5% y garantía 5%, PLANOS
        // (sin tramos), modificando los Arts. 8, 15, 16 y 17 de la RND 102500000019.
        return ['pi'=>0.05, 'gar'=>0.05];
    }

    /** UFV real (ufv_api) si está disponible; si no, proyectada (Res.116/2001). */
    public function ufv($fecha) {
        if (isset($this->ufvCache[$fecha])) return $this->ufvCache[$fecha];
        if ($this->proy === null) $this->calibrarProyeccion();
        // Fechas posteriores al último dato conocido: proyectar sin consultar la API.
        if ($this->proy && strtotime($fecha) > strtotime($this->proy['lastDate'])) {
            return $this->ufvCache[$fecha] = $this->proyectar($fecha);
        }
        $v = $this->apiUFV($fecha);
        if ($v !== null) return $this->ufvCache[$fecha] = $v;
        if ($this->proy) return $this->ufvCache[$fecha] = $this->proyectar($fecha);
        return null;
    }

    /**
     * Proyecta la UFV con la metodología del BCB (Res. de Directorio 116/2001):
     *   UFV_d = UFV_(d-1) · (1 + π)^(1/n)
     * con π = inflación interanual MENSUALIZADA (raíz doceava de la anual) y
     * n = días calendario del mes. La inflación interanual se estima de la propia
     * UFV (UFV_último / UFV_hace_1_año − 1), pues la UFV ya acumula la inflación.
     */
    private function proyectar($fecha) {
        if (!$this->proy) return null;
        $ts = strtotime($this->proy['lastDate']);
        $end = strtotime($fecha);
        $ufv = $this->proy['lastUFV'];
        $pm = $this->proy['pmensual'];
        $guard = 0;
        while ($ts < $end && $guard < 4000) {
            $ts = strtotime('+1 day', $ts);
            $n = (int)date('t', $ts);              // días calendario del mes
            $ufv *= pow(1 + $pm, 1.0/$n);
            $guard++;
        }
        return round($ufv, 5);
    }

    /** Precarga en caché todas las UFV del rango con una sola llamada (get_range). */
    private function prefetch($from, $to) {
        $ch = curl_init(self::UFV_API.'?action=get_range&from='.$from.'&to='.$to);
        curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_SSL_VERIFYPEER=>false,
            CURLOPT_TIMEOUT=>15, CURLOPT_CONNECTTIMEOUT=>5]);
        $r = curl_exec($ch); curl_close($ch);
        $d = json_decode($r, true);
        if (is_array($d) && !empty($d['rates'])) {
            foreach ($d['rates'] as $date=>$rate) { $v=round(floatval($rate),5); if ($v>1&&$v<10) $this->ufvCache[$date]=$v; }
        }
    }

    /** Calibra la proyección con get_latest (1 llamada) + un dato ~1 año antes. */
    private function calibrarProyeccion() {
        $ch = curl_init(self::UFV_API.'?action=get_latest');
        curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_SSL_VERIFYPEER=>false,
            CURLOPT_TIMEOUT=>8, CURLOPT_CONNECTTIMEOUT=>4]);
        $r = curl_exec($ch); curl_close($ch);
        $d = json_decode($r, true);
        $last = $d['date'] ?? null; $lastV = isset($d['rate']) ? floatval($d['rate']) : null;
        if (!$last || !$lastV) { $this->proy = false; return; }
        $prevF = (new DateTime($last))->modify('-365 day')->format('Y-m-d');
        $prevV = $this->apiUFV($prevF) ?: $lastV;
        $panual = $prevV>0 ? ($lastV/$prevV - 1) : 0;      // inflación 12 meses (UFV interanual)
        $pmensual = pow(1 + $panual, 1.0/12) - 1;          // mensualizada (raíz 12) — Res.116/2001
        $this->proy = ['lastDate'=>$last, 'lastUFV'=>round($lastV,5), 'panual'=>$panual, 'pmensual'=>$pmensual];
    }

    /** Deuda actualizada (TO + MV + interés, sin sanción) a la fecha dada. */
    public function DA($to, $venc, $fecha) {
        $ufvVenc = $this->ufv($venc);
        $ufvPago = $this->ufv($fecha);
        $r = $this->motor->calcular([
            'tributoOmitido'=>$to, 'fechaVenc'=>$venc, 'fechaPago'=>$fecha,
            'ufvVenc'=>$ufvVenc, 'ufvPago'=>$ufvPago,
            'sancionOmision'=>false, 'multaDF'=>0, 'precisionFactor'=>'completa',
        ]);
        return (is_array($r) && empty($r['error'])) ? $r : null;
    }

    /** Último día hábil del mes de $ts (timestamp), retrocediendo si feriado/finde. */
    public static function ultimoHabilMes($y, $m) {
        $ts = mktime(0,0,0, $m+1, 0, $y); // último día del mes m
        $fer = Vencimientos::feriados((int)date('Y',$ts));
        $g=0;
        while (!Vencimientos::esHabil($ts, $fer)) { $ts = strtotime('-1 day',$ts); if(++$g>10) break; }
        return date('Y-m-d', $ts);
    }

    /**
     * Calcula la FAP completa.
     * @param array $in {
     *   fechaSolicitud: 'Y-m-d',
     *   mesPrimeraCuota?: int (desfase de meses desde solicitud a 1ª cuota; def 2),
     *   deudas: [ {impuesto, periodo, fechaVenc?, tributoOmitido, cuotas, multaDF?} ],
     *   tipoGarantia?: 'efectivo'|'valores'|'sin'
     * }
     */
    public function calcular($in) {
        $sol = $in['fechaSolicitud'] ?? null;
        $deudas = $in['deudas'] ?? [];
        if (!$sol || !$deudas) return ['error'=>'Faltan datos: fecha de solicitud y al menos una deuda.'];
        $offset = isset($in['mesPrimeraCuota']) ? (int)$in['mesPrimeraCuota'] : 2;

        // 1) Accesorios + total por deuda (DA a la fecha de solicitud)
        $det = [];
        $totalConsolidado = 0;
        foreach ($deudas as $d) {
            $to = floatval($d['tributoOmitido'] ?? 0);
            $venc = $d['fechaVenc'] ?? null;
            if (!$venc) return ['error'=>"Falta fecha de vencimiento de una deuda ({$d['impuesto']} {$d['periodo']})."];
            $daSol = $this->DA($to, $venc, $sol);
            if (!$daSol) return ['error'=>"No se pudo calcular accesorios de {$d['impuesto']} {$d['periodo']} (UFV no disponible)."];
            $total = $daSol['totalBs'];
            $det[] = [
                'impuesto'=>$d['impuesto'] ?? '', 'periodo'=>$d['periodo'] ?? '',
                'to'=>$to, 'venc'=>$venc, 'cuotas'=>(int)($d['cuotas'] ?? 1),
                'daSol'=>$daSol, 'total'=>$total,
                'mvSol'=>$daSol['mvBs'], 'intSol'=>$daSol['interesBs'],
            ];
            $totalConsolidado += $total;
        }
        $totalConsolidado = round($totalConsolidado, 2);

        // 2) Porcentajes y montos iniciales (según total consolidado)
        $pct = self::porcentajes($totalConsolidado);
        // Garantía en efectivo/valores se descuenta del saldo; hipotecaria/primer
        // requerimiento ('sin') no se descuenta (es un respaldo, no pago).
        $descuentaGar = (($in['tipoGarantia'] ?? 'efectivo') !== 'sin');
        $garPct = $descuentaGar ? $pct['gar'] : 0.0;
        $pagoInicialTotal = round($totalConsolidado * $pct['pi'], 2);
        $garantiaTotal    = round($totalConsolidado * $garPct, 2);

        // 3) Por deuda: pago inicial/garantía proporcional, saldo y base
        foreach ($det as &$x) {
            $x['pagoInicial'] = round($x['total'] * $pct['pi'], 2);
            $x['garantia']    = round($x['total'] * $garPct, 2);
            $x['saldo']       = round($x['total'] - $x['pagoInicial'] - $x['garantia'], 2);
            $x['base']        = $x['saldo'] / $x['cuotas']; // base por cuota (en Bs a la solicitud)
        }
        unset($x);
        $saldoTotal = round(array_sum(array_column($det,'saldo')), 2);

        // 4) Cronograma de fechas (último día hábil del mes)
        // Tope de 30 cuotas (Art. 5) — se acota cada deuda y el plan.
        foreach ($det as &$x) { if ($x['cuotas'] > 30) $x['cuotas'] = 30; if ($x['cuotas'] < 1) $x['cuotas'] = 1; $x['base'] = $x['saldo'] / $x['cuotas']; }
        unset($x);
        $maxN = min(max(array_column($det,'cuotas')), 30);
        $cronograma = [];
        $dt = new DateTime($sol);

        // Cuota mínima 200 UFV (Art. 10), sobre el saldo (ya sin pago inicial/garantía).
        $ufvSol = $this->ufv($sol);
        $cuotaMinUFV = 200.0;
        $cuotaMinBs = $ufvSol ? round($cuotaMinUFV * $ufvSol, 2) : null;
        $saldoUFV = ($ufvSol && $ufvSol>0) ? $saldoTotal / $ufvSol : null;
        $maxCuotasPermitidas = $saldoUFV ? min(30, max(1, (int)floor($saldoUFV / $cuotaMinUFV))) : 30;

        // Calibra la proyección (1-2 llamadas) y precarga las UFV reales del rango
        // en una sola llamada (get_range), acotando 'to' al último dato conocido.
        if ($this->proy === null) $this->calibrarProyeccion();
        $fromTs = strtotime($sol);
        foreach ($det as $x) { $t = strtotime($x['venc']); if ($t < $fromTs) $fromTs = $t; }
        $toReal = ($this->proy ? $this->proy['lastDate'] : date('Y-m-d'));
        $this->prefetch(date('Y-m-d',$fromTs), $toReal);

        for ($k=1; $k<=$maxN; $k++) {
            // mes de la cuota k = mes solicitud + offset + (k-1)
            $mc = (clone $dt)->modify('first day of this month')->modify('+'.($offset + $k - 1).' month');
            $fecha = self::ultimoHabilMes((int)$mc->format('Y'), (int)$mc->format('n'));

            $filas = []; $totCuota = 0;
            $compTO = 0; $compMV = 0; $compInt = 0;
            foreach ($det as $x) {
                if ($k > $x['cuotas']) continue; // deuda ya terminó
                $da = $this->DA($x['to'], $x['venc'], $fecha);
                if (!$da) continue;
                $cuota = round($x['base'] * $da['totalBs'] / $x['daSol']['totalBs'], 2);
                // composición de ESTA cuota: proporción TO/MV/interés del DA a la fecha
                $tot = $da['totalBs'] ?: 1;
                $pTO = $da['toActualizadoBs'] / $tot;  // tributo actualizado (TO+MV) ... separar:
                $pMV = $da['mvBs'] / $tot;
                $pToPuro = ($da['toActualizadoBs'] - $da['mvBs']) / $tot;
                $pInt = $da['interesBs'] / $tot;
                $cTO = round($cuota * $pToPuro, 2);
                $cMV = round($cuota * $pMV, 2);
                $cInt = round($cuota * $pInt, 2);
                $filas[] = [
                    'impuesto'=>$x['impuesto'], 'periodo'=>$x['periodo'],
                    'cuota'=>$cuota, 'to'=>$cTO, 'mv'=>$cMV, 'interes'=>$cInt,
                ];
                $totCuota += $cuota; $compTO += $cTO; $compMV += $cMV; $compInt += $cInt;
            }
            $ufvF = $this->ufv($fecha);
            $minBsF = $ufvF ? round($cuotaMinUFV * $ufvF, 2) : null;
            $cronograma[] = [
                'n'=>$k, 'fecha'=>$fecha,
                'ufv'=>$ufvF,
                'proyectada'=>($this->proy && strtotime($fecha) > strtotime($this->proy['lastDate'])),
                'minBs'=>$minBsF,
                'bajoMinimo'=>($minBsF !== null && round($totCuota,2) < $minBsF),
                'vencida'=>(strtotime($fecha) <= time()),       // ya transcurrida (UFV cierta)
                'totalCuota'=>round($totCuota,2),
                'deudasActivas'=>count($filas),
                'compTO'=>round($compTO,2), 'compMV'=>round($compMV,2), 'compInt'=>round($compInt,2),
                'detalle'=>$filas,
            ];
        }

        // Avisos de validación (cuota mínima 200 UFV / tope 30 meses).
        $avisos = [];
        $nBajo = count(array_filter($cronograma, function($c){ return !empty($c['bajoMinimo']); }));
        if ($nBajo > 0) {
            $avisos[] = "Hay $nBajo cuota(s) por debajo del mínimo de 200 UFV (".
                ($cuotaMinBs!==null?('≈ Bs '.number_format($cuotaMinBs,2,',','.')):'').
                "). El SIN exige reducir el número de cuotas. Máximo recomendado: $maxCuotasPermitidas cuotas.";
        }
        if ($maxN >= 30) $avisos[] = "El plan usa el máximo de 30 meses permitido (Art. 5).";

        // Resumen vencidas (UFV cierta) vs por venir (UFV proyectada).
        $venc = array_filter($cronograma, function($c){ return !empty($c['vencida']); });
        $porVenir = array_filter($cronograma, function($c){ return empty($c['vencida']); });
        $totalVencido  = round(array_sum(array_column($venc, 'totalCuota')), 2);
        $totalPorVenir = round(array_sum(array_column($porVenir, 'totalCuota')), 2);

        return [
            'error'=>false,
            'fechaSolicitud'=>$sol,
            'ufvSolicitud'=>$ufvSol,
            'cuotaMinimaUFV'=>$cuotaMinUFV,
            'cuotaMinimaBs'=>$cuotaMinBs,
            'maxCuotasPermitidas'=>$maxCuotasPermitidas,
            'inflacionAnualEstim'=>($this->proy ? round($this->proy['panual']*100,2) : null),
            'ufvUltimaFecha'=>($this->proy ? $this->proy['lastDate'] : null),
            'hoy'=>date('Y-m-d'),
            'nVencidas'=>count($venc), 'totalVencido'=>$totalVencido,
            'nPorVenir'=>count($porVenir), 'totalPorVenir'=>$totalPorVenir,
            'avisos'=>$avisos,
            'totalConsolidado'=>$totalConsolidado,
            'porcentajePagoInicial'=>$pct['pi']*100,
            'porcentajeGarantia'=>$garPct*100,
            'pagoInicial'=>$pagoInicialTotal,
            'garantia'=>$garantiaTotal,
            'saldoTotal'=>$saldoTotal,
            'maxCuotas'=>$maxN,
            'deudas'=>array_map(function($x){ return [
                'impuesto'=>$x['impuesto'],'periodo'=>$x['periodo'],'venc'=>$x['venc'],
                'tributoOmitido'=>$x['to'],'mv'=>$x['mvSol'],'interes'=>$x['intSol'],
                'total'=>$x['total'],'pagoInicial'=>$x['pagoInicial'],'garantia'=>$x['garantia'],
                'saldo'=>$x['saldo'],'cuotas'=>$x['cuotas'],
            ]; }, $det),
            'cronograma'=>$cronograma,
        ];
    }
}
