"""Unit tests for AuthService using in-memory ports."""

import pytest
from app.application.auth_service import AuthService
from app.application.user_service import UserService
from app.core.config import Settings
from app.core.security import Argon2PasswordHasher, JwtTokenService
from app.domain.errors import AuthenticationError

from tests.fakes import InMemoryRefreshTokenRepository, InMemoryUserRepository


@pytest.fixture
def env() -> tuple[AuthService, UserService, InMemoryUserRepository]:
    users = InMemoryUserRepository()
    refresh = InMemoryRefreshTokenRepository()
    hasher = Argon2PasswordHasher()
    tokens = JwtTokenService(Settings(jwt_secret="svc-test-secret"))
    auth = AuthService(users=users, refresh_tokens=refresh, hasher=hasher, tokens=tokens)
    user_svc = UserService(users=users, refresh_tokens=refresh, hasher=hasher)
    return auth, user_svc, users


async def test_login_success_then_authenticate(
    env: tuple[AuthService, UserService, object],
) -> None:
    auth, user_svc, _ = env
    created = await user_svc.create_user(username="alice", password="password123")

    pair = await auth.login("alice", "password123")
    user = await auth.authenticate_access(pair.access.encoded)
    assert user.id == created.id
    assert user.username == "alice"


async def test_login_wrong_password(env: tuple[AuthService, UserService, object]) -> None:
    auth, user_svc, _ = env
    await user_svc.create_user(username="alice", password="password123")
    with pytest.raises(AuthenticationError):
        await auth.login("alice", "nope")


async def test_login_unknown_user(env: tuple[AuthService, UserService, object]) -> None:
    auth, _, _ = env
    with pytest.raises(AuthenticationError):
        await auth.login("ghost", "whatever")


async def test_login_inactive_user(env: tuple[AuthService, UserService, object]) -> None:
    auth, user_svc, _ = env
    user = await user_svc.create_user(username="alice", password="password123")
    await user_svc.set_active(user.id, is_active=False)
    with pytest.raises(AuthenticationError):
        await auth.login("alice", "password123")


async def test_refresh_rotates_and_invalidates_old(
    env: tuple[AuthService, UserService, object],
) -> None:
    auth, user_svc, _ = env
    await user_svc.create_user(username="alice", password="password123")
    pair = await auth.login("alice", "password123")

    new_pair = await auth.refresh(pair.refresh.encoded)
    assert new_pair.refresh.encoded != pair.refresh.encoded

    # Old refresh token is now revoked (rotation) — reuse must fail.
    with pytest.raises(AuthenticationError):
        await auth.refresh(pair.refresh.encoded)

    # New one still works.
    await auth.refresh(new_pair.refresh.encoded)


async def test_access_token_not_accepted_as_refresh(
    env: tuple[AuthService, UserService, object],
) -> None:
    auth, user_svc, _ = env
    await user_svc.create_user(username="alice", password="password123")
    pair = await auth.login("alice", "password123")
    with pytest.raises(AuthenticationError):
        await auth.refresh(pair.access.encoded)


async def test_logout_revokes_refresh(env: tuple[AuthService, UserService, object]) -> None:
    auth, user_svc, _ = env
    await user_svc.create_user(username="alice", password="password123")
    pair = await auth.login("alice", "password123")

    await auth.logout(pair.refresh.encoded)
    with pytest.raises(AuthenticationError):
        await auth.refresh(pair.refresh.encoded)


async def test_logout_ignores_garbage(env: tuple[AuthService, UserService, object]) -> None:
    auth, _, _ = env
    await auth.logout("not-a-jwt")  # must not raise
