from typing import Any

from motor.motor_asyncio import AsyncIOMotorCollection

from app.auth.jwt import TokenError, create_access_token, create_refresh_token, decode_token
from app.auth.passwords import verify_password
from app.config import Settings
from app.errors import ApiError
from app.schemas.auth import TokenResponse, UserResponse


class AuthService:
    def __init__(self, users: AsyncIOMotorCollection[dict[str, Any]], settings: Settings) -> None:
        self.users = users
        self.settings = settings

    async def authenticate(self, username: str, password: str) -> TokenResponse:
        user = await self.users.find_one({"username": username})
        if user is None or not user.get("is_active", True) or not verify_password(password, user["password_hash"]):
            raise ApiError("invalid_credentials", "Username or password is incorrect", 401)
        return self._token_pair(username)

    async def refresh(self, refresh_token: str) -> TokenResponse:
        try:
            payload = decode_token(refresh_token, self.settings, "refresh")
        except TokenError as exc:
            raise ApiError("invalid_token", "Refresh token is invalid", 401) from exc
        username = payload["sub"]
        user = await self.users.find_one({"username": username, "is_active": True})
        if user is None:
            raise ApiError("invalid_token", "Refresh token is invalid", 401)
        return self._token_pair(username)

    async def get_user(self, username: str) -> UserResponse:
        user = await self.users.find_one({"username": username})
        if user is None or not user.get("is_active", True):
            raise ApiError("user_not_found", "User does not exist", 404)
        return UserResponse(username=user["username"], is_active=user.get("is_active", True))

    def _token_pair(self, username: str) -> TokenResponse:
        return TokenResponse(
            access_token=create_access_token(username, self.settings),
            refresh_token=create_refresh_token(username, self.settings),
            expires_in=self.settings.jwt_expiration_minutes * 60,
        )
