<?php

declare(strict_types=1);

namespace Tests\Unit\Services;

use App\Models\PasswordReset;
use App\Services\AuthService;
use Tests\TestCase;

class AuthServiceTest extends TestCase
{
    private AuthService $authService;
    private PasswordReset $passwordResetModel;

    protected function setUp(): void
    {
        parent::setUp();
        $this->authService = new AuthService();
        $this->passwordResetModel = new PasswordReset();
    }

    public function testLoginWithValidCredentials(): void
    {
        $this->createTestUser('login@test.com', 'comercial_manager', 'active');

        $result = $this->authService->login('login@test.com', 'Password123!', '127.0.0.1', 'PHPUnit');

        $this->assertArrayHasKey('access_token', $result);
        $this->assertArrayHasKey('refresh_token', $result);
        $this->assertArrayHasKey('expires_in', $result);
        $this->assertArrayHasKey('user', $result);
        $this->assertEquals('login@test.com', $result['user']['email']);
        $this->assertEquals('comercial_manager', $result['user']['role']);
        $this->assertNotEmpty($result['access_token']);
    }

    public function testLoginWithInvalidEmail(): void
    {
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Credenciales incorrectas');

        $this->authService->login('noexiste@test.com', 'Password123!', '127.0.0.1', 'PHPUnit');
    }

    public function testLoginWithInvalidPassword(): void
    {
        $this->createTestUser('wrongpass@test.com', 'comercial_manager', 'active');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Credenciales incorrectas');

        $this->authService->login('wrongpass@test.com', 'WrongPassword!', '127.0.0.1', 'PHPUnit');
    }

    public function testLoginWithInactiveUser(): void
    {
        $this->createTestUser('inactive@test.com', 'comercial_manager', 'inactive');

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Usuario inactivo');

        $this->authService->login('inactive@test.com', 'Password123!', '127.0.0.1', 'PHPUnit');
    }

    public function testLoginWithPendingActivation(): void
    {
        $this->createTestUser('pending@test.com', 'comercial_manager', 'pending_activation');

        $result = $this->authService->login('pending@test.com', 'Password123!', '127.0.0.1', 'PHPUnit');

        $this->assertArrayHasKey('requires_activation', $result);
        $this->assertTrue($result['requires_activation']);
        $this->assertArrayHasKey('activation_token', $result);
        $this->assertNotEmpty($result['activation_token']);
    }

    public function testLogout(): void
    {
        $userId = $this->createTestUser('logout@test.com', 'comercial_manager', 'active');
        $result = $this->authService->login('logout@test.com', 'Password123!', '127.0.0.1', 'PHPUnit');

        $this->authService->logout($result['access_token']);

        $sessionModel = new \App\Models\UserSession();
        $session = $sessionModel->findByToken($result['access_token']);
        $this->assertNull($session);
    }

    public function testLogoutWithInvalidToken(): void
    {
        $this->authService->logout('token-inexistente');
        $this->assertTrue(true);
    }

    public function testForgotPassword(): void
    {
        $userId = $this->createTestUser('forgot@test.com', 'comercial_manager', 'active');

        $this->authService->forgotPassword('forgot@test.com');

        $tokens = $this->passwordResetModel->query(
            "SELECT * FROM password_resets WHERE user_id = :uid AND used = 0",
            ['uid' => $userId]
        );
        $this->assertCount(1, $tokens);
    }

    public function testForgotPasswordWithNonexistentEmail(): void
    {
        $this->authService->forgotPassword('noexiste@test.com');

        $tokens = $this->passwordResetModel->query(
            "SELECT * FROM password_resets WHERE used = 0"
        );
        $this->assertCount(0, $tokens);
    }

    public function testForgotPasswordInvalidatesPreviousTokens(): void
    {
        $userId = $this->createTestUser('invalidate@test.com', 'comercial_manager', 'active');

        $this->passwordResetModel->createToken($userId, 'old-token-1', 3600);
        $this->passwordResetModel->createToken($userId, 'old-token-2', 3600);

        $this->authService->forgotPassword('invalidate@test.com');

        $tokens = $this->passwordResetModel->query(
            "SELECT * FROM password_resets WHERE user_id = :uid AND used = 0",
            ['uid' => $userId]
        );
        $this->assertCount(1, $tokens);
    }

    public function testResetPassword(): void
    {
        $userId = $this->createTestUser('reset@test.com', 'comercial_manager', 'active');
        $token = bin2hex(random_bytes(32));
        $this->passwordResetModel->createToken($userId, $token, 3600);

        $this->authService->resetPassword($token, 'NewStrongPass1!');

        $userModel = new \App\Models\User();
        $user = $userModel->findById($userId);
        $this->assertTrue($userModel->verifyPassword($user, 'NewStrongPass1!'));

        $reset = $this->passwordResetModel->findValidToken($token);
        $this->assertNull($reset);
    }

    public function testResetPasswordWithInvalidToken(): void
    {
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Token inválido o expirado');

        $this->authService->resetPassword('token-falso', 'NewPass123!');
    }

    public function testResetPasswordWithUsedToken(): void
    {
        $userId = $this->createTestUser('usedtoken@test.com', 'comercial_manager', 'active');
        $token = bin2hex(random_bytes(32));
        $id = $this->passwordResetModel->createToken($userId, $token, 3600);
        $this->passwordResetModel->markAsUsed($id);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Token inválido o expirado');

        $this->authService->resetPassword($token, 'NewPass123!');
    }

    public function testActivateAccount(): void
    {
        $userId = $this->createTestUser('activate@test.com', 'comercial_manager', 'pending_activation');

        $loginResult = $this->authService->login('activate@test.com', 'Password123!', '127.0.0.1', 'PHPUnit');
        $this->assertTrue($loginResult['requires_activation']);

        $result = $this->authService->activateAccount(
            $loginResult['activation_token'],
            'NewActivePass1!'
        );

        $this->assertArrayHasKey('message', $result);
        $this->assertEquals('Cuenta activada correctamente', $result['message']);

        $loginResult2 = $this->authService->login('activate@test.com', 'NewActivePass1!', '127.0.0.1', 'PHPUnit');
        $this->assertArrayHasKey('access_token', $loginResult2);
    }

    public function testActivateAccountAlreadyActive(): void
    {
        $userId = $this->createTestUser('already@test.com', 'comercial_manager', 'active');

        $tempToken = $this->generateTempTokenDirect($userId);

        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('La cuenta ya ha sido activada');

        $this->authService->activateAccount($tempToken, 'NewPass123!');
    }

    public function testLoginAsSuperAdmin(): void
    {
        $this->createTestUser('super@test.com', 'super_admin', 'active');

        $result = $this->authService->login('super@test.com', 'Password123!', '127.0.0.1', 'PHPUnit');

        $this->assertEquals('super_admin', $result['user']['role']);
    }

    public function testLoginCreatesSession(): void
    {
        $this->createTestUser('session@test.com', 'comercial_manager', 'active');

        $result = $this->authService->login('session@test.com', 'Password123!', '127.0.0.1', 'PHPUnit');

        $sessionModel = new \App\Models\UserSession();
        $session = $sessionModel->findByToken($result['access_token']);
        $this->assertNotNull($session);
    }

    private function generateTempTokenDirect(int $userId): string
    {
        $config = require BASE_PATH . '/config/app.php';
        $payload = [
            'iss' => $config['app']['url'],
            'iat' => time(),
            'exp' => time() + 900,
            'sub' => $userId,
            'purpose' => 'activation',
        ];
        return \Firebase\JWT\JWT::encode($payload, $config['jwt']['secret'], 'HS256');
    }
}
