<?php

namespace App\Services;

use App\Models\Credit;
use App\Models\CreditAuditLog;
use App\Models\Expense;
use App\Models\FinancialOperation;
use App\Models\Income;
use App\Models\PaymentAuditLog;
use App\Models\User;
use App\Support\CreditRules;
use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;

class CreditCascadeDeletionService
{
    /**
     * Eliminación total del crédito bajo POLÍTICA A HARD.
     *
     * REGLAS:
     * - No se pueden eliminar créditos con pagos.
     * - No se pueden eliminar créditos hijos con pagos.
     * - Respeta multi-tenant (company_id) y roles (super_admin).
     * - Limpia cuotas, incomes, expenses y auditoría.
     */
    public function deleteCreditCompletely(Credit $credit): void
    {
        $user = Auth::user();

        if (! $user) {
            throw new Exception('Usuario no autenticado.');
        }

        // Autorización global vía CreditPolicy::delete
        Gate::authorize('delete', $credit);

        DB::transaction(function () use ($credit, $user) {
            $this->deleteCreditRecursively($credit, $user);
        });
    }

    /**
     * Eliminación recursiva de un crédito y sus hijos.
     */
    protected function deleteCreditRecursively(Credit $credit, User $user): void
    {
        // Refrescar y bloquear el crédito dentro de la transacción
        $query = Credit::query()
            ->whereKey($credit->id)
            ->lockForUpdate();

        // Multi-tenant: solo super_admin puede operar fuera de su company_id
        if (! $user->hasRole('super_admin')) {
            $query->where('company_id', $user->company_id);
        }

        /** @var Credit $credit */
        $credit = $query->firstOrFail();

        // =====================================================
        // POLÍTICA A PRO: defensa centralizada
        // =====================================================
        CreditRules::ensureNoCapitalPaid($credit);

        // =====================================================
        // 1) ELIMINAR PRIMERO LOS CRÉDITOS HIJOS (RECURSIVO)
        // =====================================================
        $children = $credit->children()->get();

        foreach ($children as $child) {
            // Autorización por policy para cada hijo
            Gate::authorize('delete', $child);

            // POLÍTICA A: un crédito hijo NO puede tener pagos
            if ($child->payments()->exists()) {
                throw new Exception(
                    "No se puede eliminar el crédito hijo #{$child->id} porque tiene pagos registrados (POLÍTICA A)."
                );
            }

            $this->deleteCreditRecursively($child, $user);
        }

        // =====================================================
        // 2) POLÍTICA A HARD SOBRE EL CRÉDITO ACTUAL
        // =====================================================
        if ($credit->payments()->exists()) {
            throw new Exception(
                "No se puede eliminar el crédito #{$credit->id} porque tiene pagos registrados (POLÍTICA A)."
            );
        }

        // =====================================================
        // 3) ELIMINAR INGRESOS Y EGRESOS ASOCIADOS AL CRÉDITO
        // =====================================================
        Income::where('credit_id', $credit->id)->delete();
        Expense::where('credit_id', $credit->id)->delete();

        // Eliminar las operaciones financieras del crédito (desembolso, extensión, etc.).
        // La FK es `nullOnDelete`, así que sin esto el FinancialOperation quedaba HUÉRFANO
        // (source/target_credit_id en NULL) tras borrar el crédito — un "zombie" que seguía
        // contando en métricas de desembolso (bug del crédito #399).
        FinancialOperation::where('source_credit_id', $credit->id)
            ->orWhere('target_credit_id', $credit->id)
            ->delete();

        // =====================================================
        // 4) ELIMINAR AUDITORÍAS RELACIONADAS
        // =====================================================
        // Logs de pagos asociados al crédito (por credit_id)
        PaymentAuditLog::where('credit_id', $credit->id)->delete();

        // Logs de auditoría del crédito
        CreditAuditLog::where('credit_id', $credit->id)->delete();

        // =====================================================
        // 5) ELIMINAR CUOTAS DEL CRÉDITO
        // =====================================================
        $credit->installments()->delete();

        // =====================================================
        // 6) ELIMINAR EL CRÉDITO EN SÍ
        // =====================================================
        // No se dispara ningún evento tras esto (#129): el listener original
        // (LogCreditDeletedListener) se removió como código muerto en la
        // limpieza de 2026-06-11 y nunca se repuso. Un listener que auditara
        // el borrado escribiendo en CreditAuditLog es además arquitectónicamente
        // imposible: credit_id tiene cascadeOnDelete hacia credits, así que
        // cualquier fila insertada DESPUÉS de $credit->delete() referenciando
        // este credit_id sería eliminada en cascada de inmediato (la FK ya no
        // apunta a nada). Los logs de auditoría del crédito ya se purgaron en
        // el paso 4, antes del borrado, que es el único momento en que existen
        // de forma consistente con la FK.
        $credit->delete();
    }
}
