<?php

declare(strict_types=1);

namespace HyperfTest\Cases\Unit\Model;

use App\Model\ApiKey;
use App\Model\User;
use PHPUnit\Framework\TestCase;

/**
 * @internal
 * @coversNothing
 */
class ApiKeyTest extends TestCase
{
    protected function runInCoroutine(callable $callback): void
    {
        $exception = null;
        \Swoole\Coroutine\run(static function () use ($callback, &$exception): void {
            try {
                $callback();
            } catch (\Throwable $e) {
                $exception = $e;
            }
        });
        if ($exception) {
            throw $exception;
        }
    }

    protected function createTestUser(): User
    {
        $suffix = bin2hex(random_bytes(4));

        return User::query()->create([
            'username' => 'apikey_test_' . $suffix,
            'password' => 'Pass_' . $suffix,
            'email' => 'apikey_test_' . $suffix . '@example.com',
            'status' => 1,
            'api_key_enabled' => true,
        ]);
    }

    public function test_generate_returns_plain_key_and_model(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Test Key');

            $this->assertArrayHasKey('plain_key', $result);
            $this->assertArrayHasKey('api_key', $result);
            $this->assertInstanceOf(ApiKey::class, $result['api_key']);
            $this->assertSame(64, strlen($result['plain_key']));
        });
    }

    public function test_generate_stores_sha256_hash(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Hash Test');

            $expected_hash = hash('sha256', $result['plain_key']);
            $this->assertSame($expected_hash, $result['api_key']->key_hash);
        });
    }

    public function test_generate_stores_prefix(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Prefix Test');

            $expected_prefix = substr($result['plain_key'], 0, 8);
            $this->assertSame($expected_prefix, $result['api_key']->key_prefix);
        });
    }

    public function test_find_by_plain_key_returns_valid_key(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Find Test');

            $found = ApiKey::findByPlainKey($result['plain_key']);
            $this->assertNotNull($found);
            $this->assertSame($result['api_key']->id, $found->id);
        });
    }

    public function test_find_by_plain_key_returns_null_for_invalid_key(): void
    {
        $this->runInCoroutine(function (): void {
            $found = ApiKey::findByPlainKey('invalid_key_that_does_not_exist');
            $this->assertNull($found);
        });
    }

    public function test_find_by_plain_key_excludes_disabled_key(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Disabled Test');

            $result['api_key']->enabled = false;
            $result['api_key']->save();

            $found = ApiKey::findByPlainKey($result['plain_key']);
            $this->assertNull($found);
        });
    }

    public function test_find_by_plain_key_excludes_expired_key(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Expired Test', \Carbon\Carbon::now()->subDay()->toDateTimeString());

            $found = ApiKey::findByPlainKey($result['plain_key']);
            $this->assertNull($found);
        });
    }

    public function test_key_hash_is_hidden_in_json(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Hidden Test');

            $json = $result['api_key']->toArray();
            $this->assertArrayNotHasKey('key_hash', $json);
        });
    }

    public function test_is_valid_returns_true_for_active_key(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Valid Test');

            $this->assertTrue($result['api_key']->isValid());
        });
    }

    public function test_is_valid_returns_false_for_disabled_key(): void
    {
        $this->runInCoroutine(function (): void {
            $user = $this->createTestUser();
            $result = ApiKey::generate($user->id, 'Disabled Valid');

            $result['api_key']->enabled = false;
            $this->assertFalse($result['api_key']->isValid());
        });
    }
}
