<?php

declare(strict_types=1);

namespace Tests\Feature\Pwa;

use App\Models\Client;
use App\Models\CollectionVisit;
use App\Models\Company;
use App\Models\Credit;
use App\Models\Installment;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Laravel\Sanctum\Sanctum;
use PHPUnit\Framework\Attributes\Test;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Endurecimientos defense-in-depth derivados de la auditoría de seguridad:
 *
 * (1) El filtro ?collector_id en /credits y /payments/today valida que el
 *     collector pertenezca a la empresa del solicitante. Un id de otra empresa
 *     fuerza resultado vacío en lugar de ignorarse.
 * (2) POST /visits valida que installment_id pertenezca al crédito objetivo,
 *     evitando asociar una visita a una cuota de otro crédito vía payload.
 */
class CollectorScopingHardeningTest extends TestCase
{
    use DatabaseTransactions;

    private Company $companyA;

    private User $admin;

    private User $collectorA;

    private User $collectorB; // de otra empresa

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

        Event::fake();

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

        $plan = Plan::factory()->create(['has_pwa_access' => true]);

        $this->companyA = Company::factory()->create();
        Subscription::factory()->active()->create([
            'company_id' => $this->companyA->id,
            'plan_id' => $plan->id,
            'ends_at' => now()->addYear(),
        ]);

        $this->admin = User::factory()->create(['company_id' => $this->companyA->id]);
        $this->admin->assignRole('admin');
        $this->admin = $this->admin->fresh();

        $this->collectorA = User::factory()->create(['company_id' => $this->companyA->id]);
        $this->collectorA->assignRole('collector');
        $this->collectorA = $this->collectorA->fresh();

        // Collector de OTRA empresa
        $companyB = Company::factory()->create();
        Subscription::factory()->active()->create([
            'company_id' => $companyB->id,
            'plan_id' => $plan->id,
            'ends_at' => now()->addYear(),
        ]);
        $this->collectorB = User::factory()->create(['company_id' => $companyB->id]);
        $this->collectorB->assignRole('collector');
        $this->collectorB = $this->collectorB->fresh();
    }

    /**
     * Crea un crédito activo con una cuota pendiente asignado al collector dado.
     */
    private function makeCredit(User $collector, Company $company): Credit
    {
        $client = Client::factory()->create(['company_id' => $company->id]);

        $credit = Credit::factory()->create([
            'company_id' => $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' => 1,
            'periodicity' => 'monthly',
            'start_date' => now()->subDays(5),
            'due_date' => now()->addDays(25),
        ]);

        Installment::factory()->create([
            'company_id' => $company->id,
            'credit_id' => $credit->id,
            'installment_number' => 1,
            'status' => Installment::STATUS_PENDING,
            'amount_paid' => 0,
            'total_amount' => 100_000,
            'principal_amount' => 80_000,
            'interest_amount' => 20_000,
            'principal_balance_after' => 100_000,
            'due_date' => now()->addDays(25),
        ]);

        return $credit;
    }

    // ─── (1) Filtro collector_id validado contra la empresa ──────────────────

    #[Test]
    public function admin_filtering_credits_by_foreign_company_collector_returns_empty(): void
    {
        // Crédito real en la empresa del admin.
        $this->makeCredit($this->collectorA, $this->companyA);

        Sanctum::actingAs($this->admin);

        // Sin filtro: el admin ve el crédito de su empresa.
        $this->getJson('/api/pwa/credits')
            ->assertStatus(200)
            ->assertJsonCount(1, 'data');

        // Con collector_id de OTRA empresa: resultado vacío (no se ignora el filtro).
        $this->getJson('/api/pwa/credits?collector_id='.$this->collectorB->id)
            ->assertStatus(200)
            ->assertJsonCount(0, 'data');
    }

    #[Test]
    public function admin_filtering_credits_by_own_company_collector_returns_their_credits(): void
    {
        $credit = $this->makeCredit($this->collectorA, $this->companyA);

        Sanctum::actingAs($this->admin);

        $this->getJson('/api/pwa/credits?collector_id='.$this->collectorA->id)
            ->assertStatus(200)
            ->assertJsonCount(1, 'data')
            ->assertJsonFragment(['id' => $credit->id]);
    }

    // ─── (2) installment_id debe pertenecer al crédito ───────────────────────

    #[Test]
    public function visit_with_installment_from_another_credit_is_rejected(): void
    {
        $creditOne = $this->makeCredit($this->collectorA, $this->companyA);
        $creditTwo = $this->makeCredit($this->collectorA, $this->companyA);
        $foreignInstallment = $creditTwo->installments()->first();

        Sanctum::actingAs($this->collectorA);

        $this->postJson('/api/pwa/visits', [
            'credit_id' => $creditOne->id,
            'installment_id' => $foreignInstallment->id, // cuota de OTRO crédito
            'visit_type' => CollectionVisit::TYPE_NO_PAYMENT,
            'visit_date' => Carbon::today()->toDateString(),
            'idempotency_key' => Str::uuid()->toString(),
        ])
            ->assertStatus(422)
            ->assertJsonValidationErrors('installment_id');
    }

    #[Test]
    public function visit_with_installment_belonging_to_the_credit_is_accepted(): void
    {
        $credit = $this->makeCredit($this->collectorA, $this->companyA);
        $installment = $credit->installments()->first();

        Sanctum::actingAs($this->collectorA);

        $this->postJson('/api/pwa/visits', [
            'credit_id' => $credit->id,
            'installment_id' => $installment->id,
            'visit_type' => CollectionVisit::TYPE_NO_PAYMENT,
            'visit_date' => Carbon::today()->toDateString(),
            'idempotency_key' => Str::uuid()->toString(),
        ])->assertStatus(201);
    }
}
