"""
Unit tests for Persistent SQLite Account Database, Bank-Grade Security,
Device Remember-Me, and Level 1 Character Creation with Season Persistence.
Following Autonomous TDD Closed-Loop standards.
"""

import os
import time
import pytest
from pathlib import Path
from typing import Dict, Any

from server.auth.models import (
    Account,
    AccountStatus,
    Role,
    AuthTokenPair,
)
from server.auth.crypto import PasswordHasher, TokenService
from server.auth.captcha import CaptchaEngine
from server.auth.email_verification import EmailVerificationService, MockEmailSender
from server.auth.oauth import OAuthService
from server.auth.database import DatabaseAccountRepository
from server.auth.bank_security import (
    BankGradeSecurityEngine,
    SecurityEventType,
    DeviceFingerprint,
    RememberTokenResult,
)
from server.auth.character_service import (
    CharacterService,
    Character,
    CharacterClass,
    CharacterCreationResult,
)
from server.auth.auth_service import AuthService


class TestDatabaseAccountRepository:
    def test_database_initialization_and_account_crud(self, tmp_path: Path) -> None:
        db_path = str(tmp_path / "test_auth.db")
        repo = DatabaseAccountRepository(db_path=db_path)

        # Create account
        hasher = PasswordHasher()
        pwd_hash = hasher.hash_password("BankGrade#Exile2026")
        account = repo.create_account(
            email="emperor@freeexile.io",
            password_hash=pwd_hash,
            username="EmperorSword",
            status=AccountStatus.ACTIVE,
        )
        assert account.account_id.startswith("acc_")
        assert account.email == "emperor@freeexile.io"

        # Lookup by email, ID, and username
        acc_by_email = repo.get_by_email("emperor@freeexile.io")
        assert acc_by_email is not None
        assert acc_by_email.account_id == account.account_id

        acc_by_id = repo.get_by_id(account.account_id)
        assert acc_by_id is not None
        assert acc_by_id.username == "EmperorSword"

        acc_by_user = repo.get_by_username("EmperorSword")
        assert acc_by_user is not None

        # Case-insensitive email lookup
        acc_caps = repo.get_by_email("EMPEROR@FREEEXILE.IO")
        assert acc_caps is not None

    def test_database_refresh_token_persistence(self, tmp_path: Path) -> None:
        db_path = str(tmp_path / "test_auth_tokens.db")
        repo = DatabaseAccountRepository(db_path=db_path)

        account = repo.create_account(
            email="token_user@freeexile.io",
            password_hash="hash_dummy",
            username="TokenUser",
            status=AccountStatus.ACTIVE,
        )

        repo.store_refresh_token("rf_token_12345", account.account_id)
        found_acc = repo.get_account_by_refresh_token("rf_token_12345")
        assert found_acc is not None
        assert found_acc.account_id == account.account_id

        repo.revoke_refresh_token("rf_token_12345")
        assert repo.get_account_by_refresh_token("rf_token_12345") is None


class TestBankGradeSecurityEngine:
    def test_remember_me_token_lifecycle_and_device_binding(self, tmp_path: Path) -> None:
        db_path = str(tmp_path / "test_bank_security.db")
        repo = DatabaseAccountRepository(db_path=db_path)
        sec_engine = BankGradeSecurityEngine(repository=repo)

        account = repo.create_account(
            email="vip_bank@freeexile.io",
            password_hash="secure_hash",
            username="VipPlayer",
            status=AccountStatus.ACTIVE,
        )

        device = DeviceFingerprint(
            device_id="iphone_16_pro_max_001",
            device_name="iPhone 16 Pro Max",
            hardware_hash="hw_a18pro_metal3_9948",
            client_ip="113.161.45.22",
        )

        # 1. Issue remember token
        rem_res = sec_engine.issue_remember_token(
            account_id=account.account_id,
            device=device,
            valid_days=30,
        )
        assert rem_res.success is True
        assert rem_res.raw_token is not None

        # 2. Authenticate with valid remember token and same device
        auth_res = sec_engine.authenticate_remember_token(
            raw_token=rem_res.raw_token,
            current_device=device,
        )
        assert auth_res.success is True
        assert auth_res.account_id == account.account_id

        # 3. Hijack attempt: same token presented from different/tampered device fingerprint
        hacker_device = DeviceFingerprint(
            device_id="iphone_16_pro_max_001",
            device_name="Emulator BlueStacks",
            hardware_hash="hw_tampered_x86",
            client_ip="45.12.99.1",
        )
        hijack_res = sec_engine.authenticate_remember_token(
            raw_token=rem_res.raw_token,
            current_device=hacker_device,
        )
        assert hijack_res.success is False
        assert "fingerprint mismatch" in hijack_res.message.lower()

        # 4. Revocation
        sec_engine.revoke_remember_token(rem_res.raw_token)
        revoked_check = sec_engine.authenticate_remember_token(
            raw_token=rem_res.raw_token,
            current_device=device,
        )
        assert revoked_check.success is False

    def test_progressive_lockout_and_audit_logging(self, tmp_path: Path) -> None:
        db_path = str(tmp_path / "test_audit.db")
        repo = DatabaseAccountRepository(db_path=db_path)
        sec_engine = BankGradeSecurityEngine(repository=repo)

        account = repo.create_account(
            email="target@freeexile.io",
            password_hash="pwd",
            username="TargetAcc",
            status=AccountStatus.ACTIVE,
        )

        device = DeviceFingerprint(
            device_id="dev_001",
            device_name="Browser",
            hardware_hash="hw_001",
            client_ip="192.168.1.1",
        )

        # Record 4 failures -> progressive delays
        for i in range(1, 5):
            delay, is_locked = sec_engine.record_login_failure(account.account_id, device)
            assert is_locked is False
            assert delay >= 0

        # 5th failure -> bank-grade 15-minute lockout
        delay, is_locked = sec_engine.record_login_failure(account.account_id, device)
        assert is_locked is True
        assert delay >= 900.0

        # Check audit log records (logs are ordered by timestamp DESC, so logs[0] is latest)
        logs = sec_engine.get_audit_logs_for_account(account.account_id)
        assert len(logs) >= 5
        assert logs[0]["event_type"] == SecurityEventType.LOCKOUT.value


class TestCharacterCreationAndSeasonPersistence:
    def test_create_level_1_character_from_scratch(self, tmp_path: Path) -> None:
        db_path = str(tmp_path / "test_char.db")
        repo = DatabaseAccountRepository(db_path=db_path)
        char_service = CharacterService(repository=repo)

        account = repo.create_account(
            email="new_player@freeexile.io",
            password_hash="pass",
            username="NoviceHero",
            status=AccountStatus.ACTIVE,
        )

        # Account starts with 0 characters
        chars_initial = char_service.list_characters(account.account_id)
        assert len(chars_initial) == 0

        # Create Level 1 character bound to Season 1
        create_res = char_service.create_character(
            account_id=account.account_id,
            name="Vô Danh Kiếm",
            class_type=CharacterClass.SWORD_MASTER,
            season_id="season_01_minh_nguyet",
        )
        assert create_res.success is True
        assert create_res.character is not None

        char = create_res.character
        assert char.level == 1
        assert char.experience == 0
        assert char.current_hp == 100
        assert char.max_hp == 100
        assert char.current_mana == 50
        assert char.max_mana == 50
        assert char.season_id == "season_01_minh_nguyet"
        # Starting home location is clean sanctuary or starting zone with canonical NPCs
        assert char.current_zone_id == "zone_boundless_sanctuary"

        # Duplicate character name is prohibited
        dup_res = char_service.create_character(
            account_id=account.account_id,
            name="Vô Danh Kiếm",
            class_type=CharacterClass.FERAL_BERSERKER,
            season_id="season_01_minh_nguyet",
        )
        assert dup_res.success is False
        assert "already taken" in dup_res.message.lower()

        # Listing characters returns the newly created Level 1 character
        chars = char_service.list_characters(account.account_id)
        assert len(chars) == 1
        assert chars[0].name == "Vô Danh Kiếm"
        assert chars[0].level == 1


class TestFullIntegratedAuthWithPersistentDBAndRememberMe:
    def test_register_login_remember_me_and_character_flow(self, tmp_path: Path) -> None:
        db_path = str(tmp_path / "integrated_auth.db")
        repo = DatabaseAccountRepository(db_path=db_path)
        hasher = PasswordHasher()
        token_service = TokenService(secret_key="secret_test_key_freeexile_2026_super_secure_32bytes")
        captcha_engine = CaptchaEngine(hmac_secret="captcha_test_secret_123")
        email_sender = MockEmailSender()
        email_service = EmailVerificationService(
            email_sender=email_sender,
            code_ttl_seconds=300,
            cooldown_seconds=60,
        )
        oauth_service = OAuthService(repository=repo, token_service=token_service)
        sec_engine = BankGradeSecurityEngine(repository=repo)
        char_service = CharacterService(repository=repo)

        auth_system = AuthService(
            repository=repo,
            hasher=hasher,
            token_service=token_service,
            captcha_engine=captcha_engine,
            email_service=email_service,
            oauth_service=oauth_service,
            security_engine=sec_engine,
            character_service=char_service,
        )

        # 1. Quick Register
        captcha = auth_system.request_captcha(client_ip="127.0.0.1")
        parts = captcha.question.split()
        a, op, b = int(parts[0]), parts[1], int(parts[2])
        ans = a + b if op == "+" else (a - b if op == "-" else a * b)

        reg_res = auth_system.quick_register(
            email="persisted_warrior@freeexile.io",
            password="SecurePass#Exile2026",
            username="PersistedBlade",
            captcha_id=captcha.challenge_id,
            captcha_solution=str(ans),
            client_ip="127.0.0.1",
        )
        assert reg_res.success is True

        # 2. Verify Email OTP
        otp_record = auth_system.email_service.get_active_record("persisted_warrior@freeexile.io")
        assert otp_record is not None

        act_res = auth_system.verify_email(
            email="persisted_warrior@freeexile.io",
            verification_code=otp_record.code,
        )
        assert act_res.success is True

        # 3. Login with Remember Me checked
        device = DeviceFingerprint(
            device_id="iphone_client_01",
            device_name="iPhone 16 iOS Native",
            hardware_hash="hw_apple_a18_secure_enclave",
            client_ip="127.0.0.1",
        )

        login_res = auth_system.login(
            email="persisted_warrior@freeexile.io",
            password="SecurePass#Exile2026",
            client_ip="127.0.0.1",
            remember_me=True,
            device_fingerprint=device,
        )
        assert login_res.success is True
        assert login_res.remember_token is not None

        # 4. Next session: 1-click login using remember token
        fast_login_res = auth_system.login_with_remember_token(
            remember_token=login_res.remember_token,
            device_fingerprint=device,
        )
        assert fast_login_res.success is True
        assert fast_login_res.account_id == login_res.account_id

        # 5. Create character level 1 from scratch
        char_res = auth_system.character_service.create_character(
            account_id=login_res.account_id,
            name="Cổ Kiếm Đoạn Hồn",
            class_type=CharacterClass.SWORD_MASTER,
            season_id="season_01_minh_nguyet",
        )
        assert char_res.success is True
        assert char_res.character.level == 1
        assert char_res.character.season_id == "season_01_minh_nguyet"
