<?php

declare(strict_types=1);

namespace Apexl\Tests\User\UnitTests;

use apexl\Io\modules\user\enums\permissions\Common;
use apexl\Io\modules\user\services\PermissionDenialPayload;
use apexl\Io\modules\user\services\Permissions;
use Apexl\Tests\User\Helpers\TestPermissionsEnum;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

/**
 * The wire contract PermissionMiddleware returns for a denied data route.
 * Pure — deliberately a plain TestCase (no container, no DB).
 */
final class PermissionDenialPayloadTest extends TestCase
{
    private function registry(): Permissions
    {
        $registry = new Permissions();
        $registry->permissions = [];

        return $registry;
    }

    #[Test]
    public function body_uses_the_house_message_shape(): void
    {
        $body = PermissionDenialPayload::build(TestPermissionsEnum::HAS_PERMISSION, true, $this->registry());

        self::assertFalse($body['success']);
        self::assertSame('permission_denied', $body['error']);
        self::assertArrayHasKey('permission_denied', $body['messages']);

        $message = $body['messages']['permission_denied'];
        self::assertSame('error', $message['type']);
        self::assertSame('permission_denied', $message['id']);
        self::assertIsString($message['content']);
        self::assertNotSame('', $message['content']);
    }

    #[Test]
    public function message_id_is_stable_so_repeat_denials_collapse_to_one_toast(): void
    {
        $first  = PermissionDenialPayload::build(TestPermissionsEnum::HAS_PERMISSION, true);
        $second = PermissionDenialPayload::build(TestPermissionsEnum::DOES_NOT_HAVE_PERMISSION, true);

        self::assertSame(
            $first['messages']['permission_denied']['id'],
            $second['messages']['permission_denied']['id'],
        );
    }

    #[Test]
    public function logged_in_denial_names_the_registered_friendly_permission(): void
    {
        $registry = $this->registry();
        $registry->registerPermission(TestPermissionsEnum::HAS_PERMISSION, 'Manage users', 'Admin');

        $content = PermissionDenialPayload::content(TestPermissionsEnum::HAS_PERMISSION, true, $registry);

        self::assertStringContainsString('Manage users', $content);
        self::assertStringContainsString('administrator', $content);
    }

    #[Test]
    public function unregistered_permission_falls_back_to_a_humanised_label_never_the_fqcn(): void
    {
        $label = PermissionDenialPayload::label(TestPermissionsEnum::HAS_PERMISSION, $this->registry());

        self::assertSame('TestPermissionsEnum: Has Permission', $label);
        self::assertStringNotContainsString('\\', $label);
    }

    #[Test]
    public function common_cases_drop_the_unhelpful_enum_prefix(): void
    {
        self::assertSame('Update', PermissionDenialPayload::label(Common::UPDATE, $this->registry()));
    }

    #[Test]
    public function label_survives_an_unpopulated_registry(): void
    {
        // Permissions::$permissions is an uninitialised typed property until the
        // first registerPermission() — reading it throws, and must not 500.
        self::assertSame(
            'TestPermissionsEnum: Has Permission',
            PermissionDenialPayload::label(TestPermissionsEnum::HAS_PERMISSION, new Permissions()),
        );
    }

    #[Test]
    public function anonymous_denial_leaks_nothing_about_the_permission_model(): void
    {
        $registry = $this->registry();
        $registry->registerPermission(TestPermissionsEnum::HAS_PERMISSION, 'Manage users', 'Admin');

        $body = PermissionDenialPayload::build(TestPermissionsEnum::HAS_PERMISSION, false, $registry);

        self::assertArrayNotHasKey('permission', $body);

        $content = $body['messages']['permission_denied']['content'];
        self::assertStringNotContainsString('Manage users', $content);
        self::assertStringNotContainsString('TestPermissionsEnum', $content);
        self::assertStringContainsString('signed in', $content);
    }

    #[Test]
    public function logged_in_denial_exposes_the_key_in_basename_form_not_the_fqcn(): void
    {
        $body = PermissionDenialPayload::build(TestPermissionsEnum::HAS_PERMISSION, true);

        self::assertSame(['TestPermissionsEnum__HAS_PERMISSION'], $body['permission']);
    }

    #[Test]
    public function is_logged_in_gate_always_reads_as_a_sign_in_prompt(): void
    {
        // A stale session can fail Common::IS_LOGGED_IN while the request still
        // carries a user id — "ask an administrator" would be wrong advice.
        $content = PermissionDenialPayload::content(Common::IS_LOGGED_IN, true);

        self::assertStringContainsString('signed in', $content);
    }

    #[Test]
    public function pseudo_permissions_are_never_named_as_something_to_request(): void
    {
        $body = PermissionDenialPayload::build(Common::CAN_DO_NOTHING, true, $this->registry());

        self::assertArrayNotHasKey('permission', $body);
        self::assertStringNotContainsString(
            'Can Do Nothing',
            $body['messages']['permission_denied']['content'],
        );
    }

    #[Test]
    public function an_any_of_gate_lists_every_permission_that_would_unblock_it(): void
    {
        $registry = $this->registry();
        $registry->registerPermission(TestPermissionsEnum::HAS_PERMISSION, 'Manage users', 'Admin');
        $registry->registerPermission(TestPermissionsEnum::DOES_NOT_HAVE_PERMISSION, 'Manage roles', 'Admin');

        $body = PermissionDenialPayload::build(
            [TestPermissionsEnum::HAS_PERMISSION, TestPermissionsEnum::DOES_NOT_HAVE_PERMISSION],
            true,
            $registry,
        );

        self::assertSame(
            ['TestPermissionsEnum__HAS_PERMISSION', 'TestPermissionsEnum__DOES_NOT_HAVE_PERMISSION'],
            $body['permission'],
        );
        self::assertStringContainsString(
            '"Manage users" or "Manage roles"',
            $body['messages']['permission_denied']['content'],
        );
    }

    #[Test]
    public function an_unresolvable_gate_still_produces_an_honest_message(): void
    {
        $body = PermissionDenialPayload::build(null, true, $this->registry());

        self::assertArrayNotHasKey('permission', $body);
        self::assertNotSame('', $body['messages']['permission_denied']['content']);
    }

    #[Test]
    public function body_is_json_encodable(): void
    {
        $json = json_encode(PermissionDenialPayload::build(TestPermissionsEnum::HAS_PERMISSION, true));

        self::assertIsString($json);
        self::assertSame(
            'permission_denied',
            json_decode($json, true)['messages']['permission_denied']['id'],
        );
    }
}
