<?php

declare(strict_types=1);

namespace Tests\Feature\Pwa;

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

class VoidPaymentTest extends TestCase
{
    use DatabaseTransactions;

    private Company $company;

    private User $admin;

    private User $supervisor;

    private User $collector;

    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->company = Company::factory()->create(['interest_method' => 'flat_rate']);
        Subscription::factory()->active()->create([
            'company_id' => $this->company->id,
            'plan_id' => $plan->id,
            'ends_at' => now()->addYear(),
        ]);

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

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

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

    /**
     * Crea un crédito con cuotas y un pago EFECTIVO ya registrado (distribuido).
     *
     * @return array{0: Credit, 1: Payment}
     */
    private function creditWithPayment(): array
    {
        $client = Client::factory()->create(['company_id' => $this->company->id]);

        $credit = Credit::create([
            'company_id' => $this->company->id,
            'client_id' => $client->id,
            'created_by_user_id' => $this->admin->id,
            'collector_user_id' => $this->collector->id,
            'status' => Credit::STATUS_ACTIVE,
            'amount' => 100000,
            'interest_rate' => 20,
            'installments_count' => 4,
            'periodicity' => 'weekly',
            'start_date' => Carbon::now(),
        ]);

        for ($i = 1; $i <= 4; $i++) {
            Installment::create([
                'company_id' => $this->company->id,
                'credit_id' => $credit->id,
                'installment_number' => $i,
                'status' => Installment::STATUS_PENDING,
                'due_date' => Carbon::now()->addWeeks($i),
                'principal_amount' => 25000,
                'interest_amount' => 5000,
                'total_amount' => 30000,
                'principal_balance_after' => 30000,
                'amount_paid' => 0,
            ]);
        }

        $payment = app(PaymentManager::class)->registerPaymentForCredit(
            $credit->fresh(), 30000, Carbon::now(), $this->admin->id, 'cash',
        );

        return [$credit->fresh(), $payment];
    }

    #[Test]
    public function an_admin_can_void_a_payment(): void
    {
        [$credit, $payment] = $this->creditWithPayment();
        Sanctum::actingAs($this->admin);

        $this->postJson("/api/pwa/payments/{$payment->id}/void", [
            'reason' => 'Pago mal registrado',
        ])->assertStatus(200);

        $this->assertTrue((bool) $payment->fresh()->voided, 'el pago queda anulado');
        $this->assertDatabaseHas('payments', [
            'credit_id' => $credit->id,
            'payment_method' => 'reversal',
            'original_payment_id' => $payment->id,
        ]);
    }

    #[Test]
    public function a_supervisor_cannot_void_a_payment(): void
    {
        [$credit, $payment] = $this->creditWithPayment();
        Sanctum::actingAs($this->supervisor);

        $this->postJson("/api/pwa/payments/{$payment->id}/void", ['reason' => 'x'])
            ->assertStatus(403);

        $this->assertFalse((bool) $payment->fresh()->voided);
    }

    #[Test]
    public function a_collector_cannot_void_a_payment(): void
    {
        [$credit, $payment] = $this->creditWithPayment();
        Sanctum::actingAs($this->collector);

        $this->postJson("/api/pwa/payments/{$payment->id}/void", ['reason' => 'x'])
            ->assertStatus(403);

        $this->assertFalse((bool) $payment->fresh()->voided);
    }

    #[Test]
    public function a_payment_from_another_company_returns_404(): void
    {
        [$credit, $payment] = $this->creditWithPayment();
        $otherCompany = Company::factory()->create();
        // La empresa ajena necesita una suscripción activa: si no, el middleware
        // pwa.access (EnsurePwaAccess::hasActiveSubscription) responde 403 antes
        // de llegar al controlador, y esto dejaría de probar el 404 por tenancy.
        Subscription::factory()->active()->create([
            'company_id' => $otherCompany->id,
            'plan_id' => Plan::factory()->create(['has_pwa_access' => true])->id,
            'ends_at' => now()->addYear(),
        ]);
        $otherAdmin = User::factory()->create(['company_id' => $otherCompany->id]);
        $otherAdmin->assignRole('admin');
        Sanctum::actingAs($otherAdmin->fresh());

        $this->postJson("/api/pwa/payments/{$payment->id}/void", ['reason' => 'x'])
            ->assertStatus(404);

        $this->assertFalse((bool) $payment->fresh()->voided);
    }

    #[Test]
    public function the_reason_is_required(): void
    {
        [$credit, $payment] = $this->creditWithPayment();
        Sanctum::actingAs($this->admin);

        $this->postJson("/api/pwa/payments/{$payment->id}/void", [])
            ->assertStatus(422)
            ->assertJsonValidationErrors(['reason']);

        $this->assertFalse((bool) $payment->fresh()->voided);
    }

    #[Test]
    public function an_already_voided_payment_cannot_be_voided_again(): void
    {
        [$credit, $payment] = $this->creditWithPayment();
        Sanctum::actingAs($this->admin);

        $this->postJson("/api/pwa/payments/{$payment->id}/void", ['reason' => 'primera'])
            ->assertStatus(200);

        $this->postJson("/api/pwa/payments/{$payment->id}/void", ['reason' => 'otra vez'])
            ->assertStatus(422);
    }

    #[Test]
    public function the_credit_payments_endpoint_excludes_reversal_entries(): void
    {
        [$credit, $payment] = $this->creditWithPayment();
        Sanctum::actingAs($this->admin);

        $this->postJson("/api/pwa/payments/{$payment->id}/void", ['reason' => 'anulado'])
            ->assertStatus(200);

        // Ni el pago anulado (voided) ni el asiento de reversión aparecen.
        $this->getJson("/api/pwa/credits/{$credit->id}/payments")
            ->assertStatus(200)
            ->assertJsonPath('data', []);
    }

    #[Test]
    public function the_permissions_payload_exposes_can_void_payments_only_for_admin(): void
    {
        Sanctum::actingAs($this->admin);
        $this->getJson('/api/pwa/auth/me')
            ->assertStatus(200)
            ->assertJsonPath('permissions.can_void_payments', true);

        Sanctum::actingAs($this->collector);
        $this->getJson('/api/pwa/auth/me')
            ->assertStatus(200)
            ->assertJsonPath('permissions.can_void_payments', false);
    }

    /**
     * `can_edit_clients` debe EMITIRSE desde getPermissionsForRole (el método real que
     * usa /auth/me), no quedar solo en el fallback de rol del frontend. Antes vivía en un
     * getUserPermissions() muerto (sin llamadores), ya eliminado.
     */
    #[Test]
    public function the_permissions_payload_exposes_can_edit_clients(): void
    {
        // admin/supervisor/cobrador pueden editar → el flag debe venir en true y PRESENTE.
        Sanctum::actingAs($this->admin);
        $this->getJson('/api/pwa/auth/me')
            ->assertStatus(200)
            ->assertJsonPath('permissions.can_edit_clients', true);

        Sanctum::actingAs($this->collector);
        $this->getJson('/api/pwa/auth/me')
            ->assertStatus(200)
            ->assertJsonPath('permissions.can_edit_clients', true);
    }
}
