"""Tests for password authentication strategy."""

from __future__ import annotations

import pytest

from aura_auth._core.exceptions import UserAlreadyExistsError, UserInactiveError
from aura_auth._core.protocols import StrategyProtocol
from aura_auth._core.schemas import LoginRequest, UserCreate
from aura_auth._core.security import PasswordHelper, TokenHelper
from aura_auth.backend.sqlalchemy import SQLAlchemyBackend
from aura_auth.manager import UserManager
from aura_auth.models.sqlalchemy import SQLAlchemyBaseUser
from aura_auth.strategies.password import PasswordStrategy


@pytest.fixture
def strategy(session_factory) -> PasswordStrategy:
    helper = PasswordHelper()
    tokens = TokenHelper("p" * 32, lifetime_seconds=3600)
    return PasswordStrategy(helper, tokens)


@pytest.fixture
def backend(session_factory) -> SQLAlchemyBackend:
    return SQLAlchemyBackend(session_factory, SQLAlchemyBaseUser)


@pytest.mark.asyncio
async def test_register_hashes_password(strategy: PasswordStrategy, backend: SQLAlchemyBackend) -> None:
    data = UserCreate(email="hash@example.com", password="plain-text")
    user = await strategy.register(data, backend)
    assert user.hashed_password is not None
    assert user.hashed_password != "plain-text"
    assert user.hashed_password.startswith("$2b$")


@pytest.mark.asyncio
async def test_register_duplicate_email_raises(strategy: PasswordStrategy, backend: SQLAlchemyBackend) -> None:
    data = UserCreate(email="dup@example.com", password="password123")
    await strategy.register(data, backend)
    with pytest.raises(UserAlreadyExistsError):
        await strategy.register(data, backend)


@pytest.mark.asyncio
async def test_authenticate_success(strategy: PasswordStrategy, backend: SQLAlchemyBackend) -> None:
    await strategy.register(UserCreate(email="ok@example.com", password="password123"), backend)
    user = await strategy.authenticate(
        {"email": "ok@example.com", "password": "password123"},
        backend,
    )
    assert user is not None
    assert user.email == "ok@example.com"


@pytest.mark.asyncio
async def test_authenticate_wrong_password_returns_none(
    strategy: PasswordStrategy,
    backend: SQLAlchemyBackend,
) -> None:
    await strategy.register(UserCreate(email="bad@example.com", password="password123"), backend)
    user = await strategy.authenticate(
        {"email": "bad@example.com", "password": "wrong"},
        backend,
    )
    assert user is None


@pytest.mark.asyncio
async def test_authenticate_missing_user_returns_none(strategy: PasswordStrategy, backend: SQLAlchemyBackend) -> None:
    user = await strategy.authenticate(
        {"email": "missing@example.com", "password": "password123"},
        backend,
    )
    assert user is None


@pytest.mark.asyncio
async def test_authenticate_inactive_user_raises(strategy: PasswordStrategy, backend: SQLAlchemyBackend) -> None:
    user = await strategy.register(UserCreate(email="inactive@example.com", password="password123"), backend)
    await backend.update(user, {"is_active": False})
    with pytest.raises(UserInactiveError):
        await strategy.authenticate(
            {"email": "inactive@example.com", "password": "password123"},
            backend,
        )


@pytest.mark.asyncio
async def test_hooks_called(session_factory) -> None:
    events: list[str] = []

    class RecordingStrategy(PasswordStrategy):
        async def on_after_register(self, user) -> None:  # type: ignore[override]
            events.append("register")

        async def on_after_login(self, user) -> None:  # type: ignore[override]
            events.append("login")

    backend = SQLAlchemyBackend(session_factory, SQLAlchemyBaseUser)
    helper = PasswordHelper()
    tokens = TokenHelper("p" * 32, lifetime_seconds=3600)
    verify = TokenHelper("p" * 32, lifetime_seconds=3600)
    recording = RecordingStrategy(helper, tokens)
    user_manager = UserManager(backend, recording, tokens, verify)

    await user_manager.register(UserCreate(email="hooks@example.com", password="password123"))
    await user_manager.login(LoginRequest(email="hooks@example.com", password="password123"))
    assert events == ["register", "login"]


@pytest.mark.asyncio
async def test_strategy_isinstance_protocol(strategy: PasswordStrategy) -> None:
    assert isinstance(strategy, StrategyProtocol)
