<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Models\Client;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Tests para los accessors de balance derivado de Credit.
 *
 * Regla de rendimiento: total_receivable y total_paid deben ser relation-aware
 * (mismo patrón que remaining_balance). Cuando 'installments' está eager-loaded,
 * NO deben ejecutar queries adicionales — de lo contrario producen N+1 en el
 * endpoint de sincronización (SyncController::downloadData serializa estos
 * accessors por cada crédito, hasta 150 por sync).
 */
class CreditBalanceAccessorTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private Credit $credit;

    protected function setUp(): void
    {
        parent::setUp();

        foreach (['admin', 'collector'] as $role) {
            Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
        }

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

        $this->credit = Credit::factory()->create([
            'company_id' => $this->company->id,
            'client_id' => $client->id,
            'collector_user_id' => $collector->id,
            'created_by_user_id' => $collector->id,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => 100_000,
            'installments_count' => 2,
            'periodicity' => 'monthly',
            'start_date' => now()->subDays(5),
            'due_date' => now()->addDays(25),
        ]);

        // Cuota 1: pagada por completo (50.000)
        Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $this->credit->id,
            'installment_number' => 1,
            'status' => Installment::STATUS_PAID,
            'total_amount' => 50_000,
            'amount_paid' => 50_000,
            'principal_balance_after' => 0,
        ]);

        // Cuota 2: parcial (pagado 20.000 de 50.000)
        Installment::factory()->create([
            'company_id' => $this->company->id,
            'credit_id' => $this->credit->id,
            'installment_number' => 2,
            'status' => Installment::STATUS_PARTIAL_PAID,
            'total_amount' => 50_000,
            'amount_paid' => 20_000,
            'principal_balance_after' => 30_000,
        ]);
    }

    #[Test]
    public function total_receivable_and_total_paid_return_correct_sums(): void
    {
        $credit = Credit::query()->with('installments')->find($this->credit->id);

        $this->assertEqualsWithDelta(100_000.0, $credit->total_receivable, 0.001);
        $this->assertEqualsWithDelta(70_000.0, $credit->total_paid, 0.001);
    }

    #[Test]
    public function balance_accessors_run_no_queries_when_installments_eager_loaded(): void
    {
        $credit = Credit::query()->with('installments')->find($this->credit->id);

        DB::enableQueryLog();

        // Acceder a los tres accessors de balance: ninguno debe tocar la BD
        // porque 'installments' ya está en memoria.
        $receivable = $credit->total_receivable;
        $paid = $credit->total_paid;
        $remaining = $credit->remaining_balance;

        $queries = DB::getQueryLog();
        DB::disableQueryLog();

        $this->assertCount(
            0,
            $queries,
            'total_receivable/total_paid/remaining_balance NO deben ejecutar queries cuando installments está eager-loaded (evita N+1 en sync). Queries ejecutadas: '
                .json_encode(array_column($queries, 'query'))
        );

        // Sanity: los valores siguen siendo correctos
        $this->assertEqualsWithDelta(100_000.0, $receivable, 0.001);
        $this->assertEqualsWithDelta(70_000.0, $paid, 0.001);
        $this->assertEqualsWithDelta(30_000.0, $remaining, 0.001);
    }

    #[Test]
    public function balance_accessors_fall_back_to_queries_when_not_loaded(): void
    {
        // Sin eager load: el accessor debe consultar la BD y devolver el valor correcto.
        $credit = Credit::query()->find($this->credit->id);

        $this->assertFalse($credit->relationLoaded('installments'));
        $this->assertEqualsWithDelta(100_000.0, $credit->total_receivable, 0.001);
        $this->assertEqualsWithDelta(70_000.0, $credit->total_paid, 0.001);
    }
}
