<?php

namespace Tests\Feature;

use App\Src\Encryption;
use Mtc\Core\Admin\User;
use Mtc\Core\AdminUser;
use PHPUnit\Framework\TestCase;

/**
 * Pins the admin_users model consolidation from AL2U-T136.
 *
 * The admin_users table used to have two Eloquent models: the canonical
 * Mtc\Core\Admin\User and Mtc\Core\AdminUser. Only the latter carried the boot()
 * hook that mirrors username/email into the deterministic *_hash lookup columns,
 * so saving an admin through the canonical model silently left those hashes
 * stale - a latent data-integrity bug.
 *
 * The models are now a single canonical model (Mtc\Core\Admin\User) with
 * Mtc\Core\AdminUser kept as a backwards-compatibility subclass. These tests
 * prove the hash hook now fires through BOTH class names, and that the alias
 * still refuses to mass-assign a password.
 *
 * Extends the raw PHPUnit TestCase (like BlockRegistryRegionsTest) so it uses the
 * container booted by the phpunit bootstrap rather than refreshing a fresh
 * Laravel app - the latter would swap the global container and destabilise the
 * order-dependent tests that follow.
 *
 * Requires the docker DB:
 *     make shell
 *     ./vendor/bin/phpunit --filter=AdminUserHashConsolidationTest
 */
class AdminUserHashConsolidationTest extends TestCase
{
    /** @var int[] Admin ids created during the test, removed in tearDown. */
    private array $createdAdminIds = [];

    public function test_saving_via_the_canonical_model_derives_the_username_hash(): void
    {
        $username = $this->uniqueUsername();
        $admin = $this->createAdmin(User::class, $username);

        $this->assertSame(
            Encryption::makeHash($username),
            $this->usernameHashOf($admin->id),
            'Saving through Mtc\Core\Admin\User must refresh username_hash (the bug this task fixes).'
        );
    }

    public function test_saving_via_the_backwards_compat_alias_derives_the_username_hash(): void
    {
        $username = $this->uniqueUsername();
        $admin = $this->createAdmin(AdminUser::class, $username);

        $this->assertSame(
            Encryption::makeHash($username),
            $this->usernameHashOf($admin->id),
            'Saving through the Mtc\Core\AdminUser alias must still refresh username_hash.'
        );
    }

    public function test_alias_lookup_finds_an_admin_created_through_the_canonical_model(): void
    {
        $username = $this->uniqueUsername();
        $this->createAdmin(User::class, $username);

        $found = AdminUser::whereUsername($username)->first();

        $this->assertNotNull($found, 'whereUsername via the alias must find rows written by the canonical model.');
    }

    public function test_alias_does_not_mass_assign_password(): void
    {
        // \AdminUser::update() relies on fill() ignoring a blank password so a
        // profile save does not clobber the stored hash. Guard that here.
        $admin = new AdminUser();
        $admin->fill(['username' => $this->uniqueUsername(), 'password' => 'should-be-ignored']);

        $this->assertNull($admin->getAttribute('password'), 'The alias must not expose password to mass assignment.');
    }

    private function createAdmin(string $modelClass, string $username): User
    {
        /** @var User $admin */
        $admin = new $modelClass();
        $admin->username = $username;
        $admin->password = 'x';
        $admin->email = $username . '@example.com';
        $admin->name = 'Test Admin';
        $admin->role = 0;
        $admin->save();

        $this->createdAdminIds[] = $admin->id;

        return $admin;
    }

    private function usernameHashOf(int $id): ?string
    {
        // username_hash has no cast, so it comes back as the raw stored value.
        return User::query()->find($id)?->getAttribute('username_hash');
    }

    private function uniqueUsername(): string
    {
        return 'al2u_t136_' . uniqid();
    }

    protected function tearDown(): void
    {
        if (!empty($this->createdAdminIds)) {
            User::query()->whereIn('id', $this->createdAdminIds)->delete();
        }

        parent::tearDown();
    }
}
