"""Unit tests for UserService 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 AlreadyExistsError, AuthenticationError, NotFoundError

from tests.fakes import InMemoryRefreshTokenRepository, InMemoryUserRepository


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


async def test_create_user_duplicate_username(env: tuple[UserService, AuthService]) -> None:
    user_svc, _ = env
    await user_svc.create_user(username="bob", password="password123")
    with pytest.raises(AlreadyExistsError):
        await user_svc.create_user(username="bob", password="another-one")


async def test_get_unknown_user_raises(env: tuple[UserService, AuthService]) -> None:
    import uuid

    user_svc, _ = env
    with pytest.raises(NotFoundError):
        await user_svc.get_user(uuid.uuid4())


async def test_change_password_requires_current(env: tuple[UserService, AuthService]) -> None:
    user_svc, auth = env
    user = await user_svc.create_user(username="bob", password="password123")

    with pytest.raises(AuthenticationError):
        await user_svc.change_password(
            user.id, current_password="wrong", new_password="newpassword1"
        )

    await user_svc.change_password(
        user.id, current_password="password123", new_password="newpassword1"
    )
    # New password works, old one no longer.
    await auth.login("bob", "newpassword1")
    with pytest.raises(AuthenticationError):
        await auth.login("bob", "password123")


async def test_change_password_revokes_sessions(env: tuple[UserService, AuthService]) -> None:
    user_svc, auth = env
    user = await user_svc.create_user(username="bob", password="password123")
    pair = await auth.login("bob", "password123")

    await user_svc.change_password(
        user.id, current_password="password123", new_password="newpassword1"
    )
    with pytest.raises(AuthenticationError):
        await auth.refresh(pair.refresh.encoded)


async def test_reset_password_revokes_sessions(env: tuple[UserService, AuthService]) -> None:
    user_svc, auth = env
    user = await user_svc.create_user(username="bob", password="password123")
    pair = await auth.login("bob", "password123")

    await user_svc.reset_password(user.id, new_password="adminset12")
    with pytest.raises(AuthenticationError):
        await auth.refresh(pair.refresh.encoded)
    await auth.login("bob", "adminset12")


async def test_deactivate_revokes_sessions(env: tuple[UserService, AuthService]) -> None:
    user_svc, auth = env
    user = await user_svc.create_user(username="bob", password="password123")
    pair = await auth.login("bob", "password123")

    deactivated = await user_svc.deactivate(user.id)
    assert deactivated.is_active is False
    with pytest.raises(AuthenticationError):
        await auth.refresh(pair.refresh.encoded)
