<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Filament\Resources\Credits\Pages\ViewCredit;
use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\FinancialOperation;
use App\Models\User;
use App\Services\CreditCascadeDeletionService;
use App\Services\CreditOperationService;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Livewire\Livewire;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Al eliminar un crédito, la baja debe limpiar TODO su rastro financiero, incluido el
 * `FinancialOperation` del desembolso, y hacerlo por CUALQUIER ruta de borrado.
 *
 * Antes: (1) `CreditCascadeDeletionService` no borraba el `FinancialOperation` → quedaba
 * huérfano (FK `nullOnDelete` → source/target en NULL), un "zombie" que seguía en métricas
 * de desembolso; (2) el `DeleteAction` de la vista/edición usaba el delete por defecto de
 * Filament (no el servicio) → la FK `nullOnDelete` dejaba el Expense de desembolso
 * DESVINCULADO pero vivo, corrompiendo la caja. Reproduce y fija el caso del crédito #399.
 */
class CreditCascadeDeletionFinancialTest extends TestCase
{
    use DatabaseTransactions;

    /** @return array{0: Credit, 1: FinancialOperation} */
    private function makeDisbursedCredit(): array
    {
        Role::firstOrCreate(['name' => 'admin', 'guard_name' => 'web']);

        $company = Company::factory()->create(['interest_method' => 'flat_rate']);
        $user = User::factory()->create(['company_id' => $company->id]);
        $user->assignRole('admin');
        $this->actingAs($user);
        Filament::setCurrentPanel(Filament::getDefaultPanel());

        $client = Client::factory()->create(['company_id' => $company->id]);

        // createNewCredit crea el crédito + Expense de desembolso + FinancialOperation.
        $credit = app(CreditOperationService::class)->createNewCredit([
            'company_id' => $company->id,
            'client_id' => $client->id,
            'amount' => 100000,
            'interest_rate' => 20,
            'installments_count' => 4,
            'periodicity' => 'weekly',
            'start_date' => now()->toDateString(),
        ]);

        $this->assertDatabaseHas('expenses', ['credit_id' => $credit->id, 'category' => 'disbursement']);
        $fo = FinancialOperation::where('target_credit_id', $credit->id)
            ->where('operation_type', 'credit_disbursement')
            ->firstOrFail();

        return [$credit, $fo];
    }

    private function assertFullyDeleted(int $creditId, int $foId): void
    {
        $this->assertDatabaseMissing('credits', ['id' => $creditId]);
        $this->assertDatabaseMissing('expenses', ['credit_id' => $creditId]);
        $this->assertDatabaseMissing('installments', ['credit_id' => $creditId]);
        // La FILA del FinancialOperation debe borrarse (no basta con quedar detachada).
        $this->assertDatabaseMissing('financial_operations', ['id' => $foId]);
    }

    #[Test]
    public function the_cascade_service_also_deletes_the_disbursement_financial_operation(): void
    {
        [$credit, $fo] = $this->makeDisbursedCredit();

        app(CreditCascadeDeletionService::class)->deleteCreditCompletely($credit);

        $this->assertFullyDeleted($credit->id, $fo->id);
    }

    #[Test]
    public function deleting_from_the_credit_view_page_routes_through_the_cascade(): void
    {
        [$credit, $fo] = $this->makeDisbursedCredit();

        Livewire::test(ViewCredit::class, ['record' => $credit->getRouteKey()])
            ->callAction('delete', ['reason' => 'Error de registro, nunca se desembolsó.'])
            ->assertHasNoActionErrors();

        // El delete por defecto de Filament habría dejado el Expense de desembolso
        // desvinculado (credit_id NULL) y el FinancialOperation huérfano; con el ruteo
        // por el servicio, todo el rastro financiero desaparece.
        $this->assertFullyDeleted($credit->id, $fo->id);
    }

    #[Test]
    public function deleting_a_credit_requires_a_reason(): void
    {
        [$credit, $fo] = $this->makeDisbursedCredit();

        Livewire::test(ViewCredit::class, ['record' => $credit->getRouteKey()])
            ->callAction('delete', ['reason' => ''])
            ->assertHasActionErrors(['reason']);

        // Sin motivo válido no se borra nada.
        $this->assertDatabaseHas('credits', ['id' => $credit->id]);
        $this->assertDatabaseHas('financial_operations', ['id' => $fo->id]);
    }
}
