from typing import Optional

from keycloak import KeycloakAuthenticationError, KeycloakConnectionError
from starlette.requests import Request

from api.auth.keycloak_client import Keycloak
from api.auth.keycloak_user import KeycloakUser
from api.middleware.request import get_request
from core.get_value import get_value
from core.logger import Logger
from data.schemas.administration.user import User
from data.schemas.administration.user_dao import userDao
from data.schemas.permission.role_dao import roleDao
from data.schemas.permission.role_user import RoleUser
from data.schemas.permission.role_user_dao import roleUserDao

logger = Logger(__name__)


class RouteUserExtension:
    _cached_users: dict[int, User] = {}

    @classmethod
    def _get_user_id_from_token(cls, request: Request) -> Optional[str]:
        token = cls.get_token(request)
        if not token:
            return None

        try:
            user_info = Keycloak.client.userinfo(token)
        except KeycloakAuthenticationError:
            user_info = {}
        except KeycloakConnectionError:
            user_info = {}

        return get_value(user_info, "sub", str)

    @staticmethod
    def get_token(request: Request) -> Optional[str]:
        if "Authorization" not in request.headers:
            return None

        if len(request.headers.get("Authorization").split()) < 2:
            return None

        return request.headers.get("Authorization").split()[1]

    @classmethod
    async def get_user(cls) -> Optional[User]:
        request = get_request()
        if request is None:
            return None

        dev_user = await cls.get_dev_user()
        if dev_user is not None:
            return dev_user

        user_id = cls._get_user_id_from_token(request)
        if not user_id:
            return None

        return await userDao.find_by_keycloak_id(user_id)

    @classmethod
    async def get_dev_user(cls) -> Optional[User]:
        request = get_request()
        if request is None:
            return None

        if "Authorization" not in request.headers:
            return None

        if len(request.headers.get("Authorization").split()) < 2:
            return None

        if request.headers.get("Authorization").split()[0] != "DEV-User":
            return None

        return await userDao.find_by_keycloak_id(cls.get_token(request))

    @classmethod
    async def get_user_or_default(cls) -> Optional[User]:
        user = await cls.get_user()
        if user is None:
            user = await cls.get_dev_user()
        return user

    @classmethod
    async def _create_user(cls, kc_user: KeycloakUser):
        try:
            user = await userDao.find_by_keycloak_id(kc_user.id)
            if user is not None:
                logger.warning(f"User {user.id}:{kc_user.id} already exists")
                return

            await userDao.create(User(0, kc_user.id))
            users = await userDao.get_all()
            if len(users) == 1:
                user = await userDao.get_by_keycloak_id(kc_user.id)
                admin_role = await roleDao.get_by_name("admin")
                logger.warning(f"Assigning admin role to first user {user.id}")
                await roleUserDao.create(RoleUser(0, admin_role.id, user.id))
        except Exception as e:
            logger.error("Failed to find or create user", e)

    @classmethod
    async def verify_login(cls, request: Request) -> bool:
        auth_header = request.headers.get("Authorization", None)

        if not auth_header or not auth_header.startswith("Bearer "):
            return False

        token = auth_header.split(" ")[1]
        try:
            # Verify token using Keycloak
            user_info = Keycloak.client.userinfo(token)
            if not user_info:
                return False

            user = await cls.get_user()
            if user is None:
                await cls._create_user(KeycloakUser(user_info))
                return True

            if user.deleted:
                return False
        except KeycloakAuthenticationError:
            return False

        return True
