<?php

namespace App\Services;

use App\Enums\CreditAuditEvent;
use App\Models\Credit;
use App\Models\CreditAuditLog;
use App\Services\Interest\DiminishingBalanceStrategy;
use App\Services\Interest\FlatRateProjectedStrategy;
use App\Services\Interest\FlatRateStrategy;
use App\Services\Interest\InterestCalculationStrategy;
use App\Support\Concerns\CalculatesCreditSummary;
use App\Support\CreditRules;
use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;

class CreditCalculator
{
    use CalculatesCreditSummary;

    /** @var array<string, InterestCalculationStrategy> */
    protected array $strategies = [];

    public function __construct()
    {
        $this->registerStrategy(new FlatRateStrategy);
        $this->registerStrategy(new FlatRateProjectedStrategy);
        $this->registerStrategy(new DiminishingBalanceStrategy);
    }

    /**
     * Registra una estrategia de cálculo de interés.
     */
    public function registerStrategy(InterestCalculationStrategy $strategy): void
    {
        $this->strategies[$strategy->getName()] = $strategy;
    }

    /**
     * Obtiene la estrategia por nombre.
     */
    protected function getStrategy(string $name): InterestCalculationStrategy
    {
        if (! isset($this->strategies[$name])) {
            throw new Exception("Método de interés no soportado: {$name}");
        }

        return $this->strategies[$name];
    }

    /*=========================================================
    | AUDITORÍA CENTRAL
    =========================================================*/
    protected function log(int $creditId, string|CreditAuditEvent $action, array $payload = []): void
    {
        $user = Auth::user();

        $actionValue = $action instanceof CreditAuditEvent
            ? $action->value
            : $action;

        CreditAuditLog::create([
            'credit_id' => $creditId,
            'user_id' => $user?->id,
            'company_id' => $user?->company_id,
            'action' => $actionValue,
            'data' => $payload,
        ]);
    }

    /*=========================================================
    | REGLA CENTRAL – ¿PUEDO RECALCULAR CUOTAS?
    | POLÍTICA A PRO
    =========================================================*/
    protected function assertCanRecalculate(Credit $credit): void
    {
        // Traer lo mínimo para validar
        $credit->loadMissing(['payments', 'installments']);

        // 1) NO permitir si tiene pagos EFECTIVOS. Regenerar borra y recrea las cuotas
        //    (ver generateInstallments), así que cualquier pago aplicado dejaría sus
        //    pivotes huérfanos → se prohíbe con pagos reales. Se usa CreditRules::hasPayments()
        //    (no el crudo `payments()->exists()`) para EXCLUIR los pagos anulados y las
        //    reversas: tras anular el único pago para poder editar, esas filas mantenían
        //    el crédito bloqueado (#387). Un crédito sin pagos efectivos sí puede recalcularse.
        if (CreditRules::hasPayments($credit)) {
            throw new Exception(
                'No se puede recalcular el plan porque el crédito ya tiene pagos registrados.'
            );
        }

        // 2) NO permitir si está cerrado
        if ($credit->isClosed()) {
            throw new Exception(
                'No se pueden recalcular cuotas de un crédito cerrado.'
            );
        }

        // 3) Créditos HIJOS:
        //    AHORA SÍ se permite recalcular SI NO tienen pagos propios.
        //    (Si quisieras bloquearlos siempre, aquí pondrías la validación,
        //     pero la hemos removido porque tu flujo requiere recalcular
        //     créditos hijo sin pagos).
    }

    /*=========================================================
    | REGENERAR CUOTAS — POLÍTICA A PRO
    | - Usa generateInstallments() con flag de "regeneración"
    =========================================================*/
    public function regenerateInstallments(Credit $credit): void
    {
        // Validación centralizada
        $this->assertCanRecalculate($credit);

        // Generar plan completo marcado como "regenerado"
        $this->generateInstallments($credit, asRegeneration: true);
    }

    /*=========================================================
    | GENERAR PLAN COMPLETO DESDE CERO
    | - asRegeneration = false → creación normal
    | - asRegeneration = true  → recalculando plan existente
    =========================================================*/
    public function generateInstallments(Credit $credit, bool $asRegeneration = false): void
    {
        $credit->loadMissing('company');

        $interestMethod = $credit->company->interest_method ?? 'flat_rate';

        // Usar Strategy Pattern para calcular las cuotas
        $strategy = $this->getStrategy($interestMethod);
        $schedule = $strategy->calculate($credit);

        DB::transaction(function () use ($credit, $schedule, $interestMethod, $asRegeneration) {

            // Eliminar cuotas existentes
            $credit->installments()->delete();

            // Crear nuevas cuotas
            $credit->installments()->createMany($schedule);

            // Actualizar fecha final (due_date)
            $last = end($schedule);
            if ($last && isset($last['due_date'])) {
                $credit->due_date = $last['due_date'];
                $credit->save();
            }

            // Refrescar estado del crédito (p.ej. overdue/active/etc.)
            if (method_exists($credit, 'refreshStatus')) {
                $credit->refreshStatus();
            }

            // Registro de auditoría:
            // - INSTALLMENTS_GENERATED para primera vez
            // - INSTALLMENTS_REGENERATED para recalculado
            $this->log(
                creditId: $credit->id,
                action: $asRegeneration
                    ? CreditAuditEvent::INSTALLMENTS_REGENERATED
                    : CreditAuditEvent::INSTALLMENTS_GENERATED,
                payload: [
                    'count' => count($schedule),
                    'method' => $interestMethod,
                    'as_regeneration' => $asRegeneration,
                ],
            );
        });
    }
}
