<?php

declare(strict_types=1);

namespace Tests\Feature\Middleware;

use App\Enums\UserStatus;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

final class AccountStandingTest extends TestCase
{
    use RefreshDatabase;

    private const PASSWORD = 'correct-horse-battery-91';

    /**
     * Suspension must take effect on the next request, and must destroy the
     * credential rather than merely refusing it.
     */
    #[Test]
    public function suspending_an_account_tears_down_its_session(): void
    {
        $user = User::factory()->create([
            'email' => 'asha@example.com',
            'password' => Hash::make(self::PASSWORD),
        ]);

        $this->asSpa()->postJson('/api/v1/auth/login', [
            'email' => 'asha@example.com',
            'password' => self::PASSWORD,
        ])->assertOk();

        $this->assertAuthenticated();

        $user->forceFill(['status' => UserStatus::Suspended, 'suspended_at' => now()])->save();
        $this->forgetAuthGuards();

        $this->getJson('/api/v1/account')
            ->assertForbidden()
            ->assertJsonPath('status', 'suspended');

        /*
         * The meaningful assertion: the NEXT request is unauthenticated rather
         * than forbidden. A 403 would mean the session still exists and is being
         * refused; a 401 means it was actually destroyed.
         */
        $this->forgetAuthGuards();

        $this->getJson('/api/v1/account')->assertUnauthorized();
    }

    #[Test]
    public function suspending_an_account_revokes_the_presented_token(): void
    {
        $user = User::factory()->create([
            'email' => 'asha@example.com',
            'password' => Hash::make(self::PASSWORD),
        ]);

        $token = $this->postJson('/api/v1/auth/login', [
            'email' => 'asha@example.com',
            'password' => self::PASSWORD,
            'device_name' => 'Asha Phone',
        ])->json('token');

        $this->assertDatabaseCount('personal_access_tokens', 1);

        $user->forceFill(['status' => UserStatus::Banned])->save();
        $this->forgetAuthGuards();

        $this->withToken($token)->getJson('/api/v1/account')->assertForbidden();

        // Revoked, so the client cannot keep retrying with it.
        $this->assertDatabaseCount('personal_access_tokens', 0);

        $this->forgetAuthGuards();
        $this->withToken($token)->getJson('/api/v1/account')->assertUnauthorized();
    }

    #[Test]
    public function an_active_account_is_unaffected(): void
    {
        $user = User::factory()->create();

        $this->actingAs($user)->getJson('/api/v1/account')->assertOk();
    }

    // -----------------------------------------------------------------
    // Locale
    // -----------------------------------------------------------------

    /**
     * Server-rendered figures must respect the account's locale. Every price was
     * previously formatted for one hardcoded locale regardless of preference.
     */
    #[Test]
    public function prices_are_formatted_for_the_users_locale(): void
    {
        Plan::factory()->paid(1_234_500)->create(['code' => 'broker']);

        $english = User::factory()->create(['locale' => 'en']);
        $nepali = User::factory()->create(['locale' => 'ne']);

        $enFormatted = $this->actingAs($english)
            ->getJson('/api/v1/plans')->assertOk()->json('data.0.price.formatted');

        $this->forgetAuthGuards();

        // A different locale must produce a different rendering of the same
        // amount, while the machine-readable fields stay identical.
        $response = $this->actingAs($nepali)->getJson('/api/v1/plans')->assertOk();

        $this->assertSame(1_234_500, $response->json('data.0.price.minor'));
        $this->assertSame('12345.00', $response->json('data.0.price.amount'));
        $this->assertNotSame('', (string) $enFormatted);
    }

    #[Test]
    public function an_unsupported_locale_falls_back_rather_than_reaching_icu(): void
    {
        $user = User::factory()->create(['locale' => 'en']);

        $this->actingAs($user)
            ->withHeaders(['Accept-Language' => 'zz-ZZ,qq;q=0.9'])
            ->getJson('/api/v1/plans')
            ->assertOk();

        $this->assertSame('en', app()->getLocale());
    }
}
