<?php

use App\Services\OtpService;
use App\Services\PersistentLoginService;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use CodeIgniter\Test\FeatureTestTrait;
use Config\Services;
use Tests\Support\Auth\ArrayOtpMailer;
use Tests\Support\Auth\AuthTestTrait;
use Tests\Support\Auth\FailingOtpMailer;

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

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

    private ArrayOtpMailer $mailer;

    protected function setUp(): void
    {
        parent::setUp();
        $this->mailer = new ArrayOtpMailer();
        Services::injectMock('otpMailer', $this->mailer);
    }

    public function testInvalidEmailRejectedOverHttp(): void
    {
        $result = $this->post('auth/send-otp', ['email' => 'not-an-email']);
        $result->assertRedirect();
        $result->assertSessionHas('error', OtpService::MSG_INVALID_EMAIL);
    }

    public function testSmtpFailureOverHttpIsFriendly(): void
    {
        Services::injectMock('otpMailer', new FailingOtpMailer());
        $result = $this->post('auth/send-otp', ['email' => 'fail@example.com']);
        $result->assertRedirect();
        $result->assertSessionHas('error', OtpService::MSG_SEND_FAIL);
    }

    public function testValidOtpLogsNewUserIntoOnboarding(): void
    {
        $email = 'fresh@example.com';
        $send  = $this->post('auth/send-otp', ['email' => $email]);
        $send->assertRedirectTo('/auth/verify-otp');

        $code = $this->mailer->lastOtp();
        $this->assertMatchesRegularExpression('/^\d{6}$/', (string) $code);

        $verify = $this->withSession(['otp_email' => $email])->post('auth/verify-otp', ['otp' => $code]);
        $verify->assertRedirectTo('/profile/onboarding');
        $verify->assertCookie($this->rememberCookieName());

        $this->seeInDatabase('users', ['email' => $email, 'profile_onboarding_completed' => 0]);
        $this->dontSeeInDatabase('email_otps', ['otp_hash' => $code]);
    }

    public function testExistingOnboardedUserRoutesToDashboard(): void
    {
        $email = 'veteran-http@example.com';
        $this->createUser($email, 1);

        $this->post('auth/send-otp', ['email' => '  Veteran-HTTP@Example.com ']);
        $code = $this->mailer->lastOtp();

        $verify = $this->withSession(['otp_email' => $email])->post('auth/verify-otp', ['otp' => $code]);
        $verify->assertRedirectTo('/dashboard');

        $count = db_connect()->table('users')->where('email', $email)->countAllResults();
        $this->assertSame(1, $count);
    }

    public function testLoggedInUserVisitingSignInIsRedirected(): void
    {
        $onboarded = $this->createUser('signed-in@example.com', 1);
        $this->withSession([
            'user_id'                      => (int) $onboarded['id'],
            'email'                        => $onboarded['email'],
            'profile_onboarding_completed' => 1,
        ])->get('sign-in')->assertRedirectTo('/dashboard');

        $newbie = $this->createUser('needs-onboarding@example.com', 0);
        $this->withSession([
            'user_id'                      => (int) $newbie['id'],
            'email'                        => $newbie['email'],
            'profile_onboarding_completed' => 0,
        ])->get('sign-in')->assertRedirectTo('/profile/onboarding');
    }

    public function testProtectedUrlWithoutSessionRedirectsToSignIn(): void
    {
        $this->withSession([])->get('dashboard')->assertRedirectTo('/sign-in');
    }

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

        $this->putRequestCookie($this->rememberCookieName(), $raw);
        $result = $this->withSession([])->get('dashboard');
        $result->assertOK();
        $result->assertSee('TIEASY');
        $this->assertStringContainsString('no-store', $result->response()->getHeaderLine('Cache-Control'));
    }

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

        $this->putRequestCookie($this->rememberCookieName(), $raw);
        $logout = $this->withSession([
            'user_id'                      => (int) $user['id'],
            'email'                        => $user['email'],
            'profile_onboarding_completed' => 1,
        ])->get('logout');

        $logout->assertRedirectTo('/sign-in');
        $this->assertStringContainsString('no-store', $logout->response()->getHeaderLine('Cache-Control'));

        $this->dontSeeInDatabase('user_login_tokens', ['selector' => $selector]);

        unset($_COOKIE[$this->rememberCookieName()]);
        $after = $this->withSession([])->get('dashboard');
        $after->assertRedirectTo('/sign-in');
        $this->assertStringContainsString('no-store', $after->response()->getHeaderLine('Cache-Control'));
    }

    public function testResendCooldownRejectedOverHttp(): void
    {
        $email = 'http-cooldown@example.com';
        $this->post('auth/send-otp', ['email' => $email])->assertRedirectTo('/auth/verify-otp');

        $resend = $this->withSession(['otp_email' => $email])->post('auth/resend-otp');
        $resend->assertRedirect();
        $resend->assertSessionHas('error', OtpService::MSG_COOLDOWN);
        $this->assertCount(1, $this->mailer->sent);
    }

    public function testOtpVerifyPageShowsSixBoxesAndEmailWording(): void
    {
        $email = 'otp-ui@example.com';
        $this->post('auth/send-otp', ['email' => $email]);

        $page = $this->withSession(['otp_email' => $email])->get('auth/verify-otp');
        $page->assertOK();
        $page->assertSee('OTP Verification');
        $page->assertSee('Resend OTP');
        $page->assertSee('ot***@example.com');
        $page->assertDontSee('Phone');
        $page->assertDontSee('SMS');
        $this->assertSame(6, substr_count($page->getBody(), 'class="otp-box"'));
    }
}
