<?php

use App\Models\UserLoginTokenModel;
use App\Services\AuthSessionService;
use App\Services\PersistentLoginService;
use CodeIgniter\Cookie\Cookie;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use Tests\Support\Auth\AuthTestTrait;

/**
 * @internal
 */
final class PersistentLoginTest extends CIUnitTestCase
{
    use DatabaseTestTrait;
    use AuthTestTrait;

    protected $namespace = 'App';
    protected $refresh   = true;
    protected $migrate   = true;
    protected $seed      = '';

    public function testPersistentTokenIsCreatedSecurelyAndRawValidatorIsNotStored(): void
    {
        $user = $this->createUser('persist@example.com', 1);
        (new PersistentLoginService())->issueToken((int) $user['id']);

        $raw = $this->readResponseCookie($this->rememberCookieName());
        $this->assertNotNull($raw);
        $this->assertMatchesRegularExpression('/^[a-f0-9]+\.[a-f0-9]+$/', $raw);

        [$selector, $validator] = explode('.', $raw, 2);

        $cookie = service('response')->getCookie($this->rememberCookieName());
        $this->assertInstanceOf(Cookie::class, $cookie);
        $this->assertTrue($cookie->isHTTPOnly());
        $this->assertSame(Cookie::SAMESITE_LAX, $cookie->getSameSite());

        $row = (new UserLoginTokenModel())->where('selector', $selector)->first();
        $this->assertIsArray($row);
        $this->assertNotSame($validator, $row['token_hash']);
        $this->assertNotSame($raw, $row['token_hash']);
        $this->assertSame(64, strlen((string) $row['token_hash']));
        $this->assertSame((int) $user['id'], (int) $row['user_id']);
    }

    public function testPersistentTokenRestoresAuthenticationAndRotates(): void
    {
        $user = $this->createUser('restore@example.com', 1);
        $svc  = new PersistentLoginService();
        $svc->issueToken((int) $user['id']);

        $issued = $this->readResponseCookie($this->rememberCookieName());
        $this->assertNotNull($issued);
        [$selector, $oldValidator] = explode('.', $issued, 2);

        $before = (new UserLoginTokenModel())->where('selector', $selector)->first();
        $this->assertIsArray($before);

        session()->destroy();
        $this->assertNull(session()->get('user_id'));

        $this->putRequestCookie($this->rememberCookieName(), $issued);
        $restored = $svc->restoreSessionIfValid();
        $this->assertTrue($restored);
        $this->assertSame((int) $user['id'], (int) session()->get('user_id'));
        $this->assertSame('restore@example.com', session()->get('email'));

        $rotated = $this->readResponseCookie($this->rememberCookieName());
        $this->assertNotNull($rotated);
        [$newSelector, $newValidator] = explode('.', $rotated, 2);
        $this->assertSame($selector, $newSelector);
        $this->assertNotSame($oldValidator, $newValidator);

        $after = (new UserLoginTokenModel())->where('selector', $selector)->first();
        $this->assertIsArray($after);
        $this->assertNotSame($before['token_hash'], $after['token_hash']);
        $this->assertNotSame($newValidator, $after['token_hash']);

        session()->destroy();
        $this->putRequestCookie($this->rememberCookieName(), $issued);
        $this->assertFalse($svc->restoreSessionIfValid());
    }

    public function testInactiveUserCannotBeRestored(): void
    {
        $user = $this->createUser('inactive@example.com', 1, 0);
        $svc  = new PersistentLoginService();
        $svc->issueToken((int) $user['id']);
        $raw = $this->readResponseCookie($this->rememberCookieName());
        $this->assertNotNull($raw);

        session()->destroy();
        $this->putRequestCookie($this->rememberCookieName(), $raw);
        $this->assertFalse($svc->restoreSessionIfValid());
        $this->assertNull(session()->get('user_id'));
        $this->assertSame(0, (new UserLoginTokenModel())->where('user_id', $user['id'])->countAllResults());
    }

    public function testLogoutRevokesPersistentToken(): void
    {
        $user = $this->createUser('logout-token@example.com', 1);
        $svc  = new PersistentLoginService();
        $svc->issueToken((int) $user['id']);
        $raw = $this->readResponseCookie($this->rememberCookieName());
        $this->assertNotNull($raw);
        [$selector] = explode('.', $raw, 2);

        $this->putRequestCookie($this->rememberCookieName(), $raw);
        $svc->revokeCurrent();

        $this->assertNull((new UserLoginTokenModel())->where('selector', $selector)->first());
        $cleared = service('response')->getCookie($this->rememberCookieName());
        $this->assertInstanceOf(Cookie::class, $cleared);
        $this->assertTrue($cleared->getExpiresTimestamp() < time());
    }

    public function testIncompleteOnboardingRoutesToProfileCompletedToDashboard(): void
    {
        $auth = new AuthSessionService();

        $newUser = $this->createUser('newbie@example.com', 0);
        $auth->establish($newUser, regenerate: false);
        $this->assertSame('/profile/onboarding', $auth->authenticatedDestination());

        $oldUser = $this->createUser('veteran@example.com', 1);
        $auth->establish($oldUser, regenerate: false);
        $this->assertSame('/dashboard', $auth->authenticatedDestination());
    }
}
