from http import HTTPStatus
from typing import Union

import inject
from flask import jsonify
from flask_jwt_extended import decode_token
from flask_jwt_extended.exceptions import JWTDecodeError
from jwt import PyJWTError

from app.extensions.utils.enum.jwt_enum import JWTEnum
from app.extensions.utils.log_helper import logger_
from core.domains.authentication.dto.auth_dto import GetBlacklistDto, GetVerificationDto
from core.domains.authentication.repository.auth_repository import (
    AuthenticationRepository,
)
from core.domains.user.entity.user_entity import BlacklistEntity, JwtEntity, UserEntity
from core.domains.user.repository.user_repository import UserRepository
from core.exceptions import UpdateFailErrorException
from core.use_case_output import UseCaseSuccessOutput, UseCaseFailureOutput, FailureType

logger = logger_.getLogger(__name__)


class AuthBaseUseCase:
    @inject.autoparams()
    def __init__(self, auth_repo: AuthenticationRepository, user_repo: UserRepository):
        self._auth_repo = auth_repo
        self._user_repo = user_repo


class AuthenticationUseCase:
    def execute(self) -> Union[UseCaseSuccessOutput, UseCaseFailureOutput]:
        return UseCaseSuccessOutput()


class LogoutUseCase(AuthBaseUseCase):
    def execute(
        self, dto: GetBlacklistDto
    ) -> Union[UseCaseSuccessOutput, UseCaseFailureOutput]:
        if not dto.user_id:
            return UseCaseFailureOutput(
                type="user_id",
                message=FailureType.NOT_FOUND_ERROR,
                code=HTTPStatus.NOT_FOUND,
            )
        self._auth_repo.create_blacklist(dto)
        blacklist: BlacklistEntity | None = self._auth_repo.get_blacklist_by_dto(dto)

        if not blacklist:
            return UseCaseFailureOutput(
                type="blacklist", message=FailureType.INTERNAL_ERROR
            )

        result = jsonify(
            logout_user=blacklist.user_id,
            blacklist_token=blacklist.access_token,
            expired_at=blacklist.expired_at,
        )

        return UseCaseSuccessOutput(value=result)


class VerificationJWTUseCase(AuthBaseUseCase):
    def execute(
        self, dto: GetVerificationDto
    ) -> Union[UseCaseSuccessOutput, UseCaseFailureOutput]:
        try:
            decoded = decode_token(dto.token, allow_expired=True)
        except JWTDecodeError as e:
            return UseCaseFailureOutput(
                type=f"Invalid Token, error: {e}",
                message=FailureType.INVALID_REQUEST_ERROR,
            )

        identify: str = decoded.get("identity")

        if identify == JWTEnum.GUEST_IDENTIFY.value:
            return UseCaseFailureOutput(
                type="guest not allowed", message=FailureType.UNAUTHORIZED_ERROR
            )

        try:
            user_id = int(identify)
            token_type = decoded.get("type")
        except TypeError:
            return UseCaseFailureOutput(
                type="user id", message=FailureType.NOT_FOUND_ERROR
            )

        if token_type != "access":
            return UseCaseFailureOutput(
                type="wrong token type, need access token",
                message=FailureType.INVALID_REQUEST_ERROR,
            )

        # Blacklist check
        blacklist_dto = GetBlacklistDto(
            user_id=user_id, access_token=dto.token.decode("UTF-8")
        )
        blacklist = self._auth_repo.get_blacklist_by_dto(dto=blacklist_dto)

        if blacklist:
            return UseCaseFailureOutput(
                type=f"Blacklist Token detected, please retry login",
                message=FailureType.UNAUTHORIZED_ERROR,
            )

        # Valid refresh_token check
        if not self._is_valid_refresh_token(user_id):
            return UseCaseFailureOutput(
                type=f"Refresh Token expired, please retry login",
                message=FailureType.UNAUTHORIZED_ERROR,
            )

        # is_out and is_available check
        user: UserEntity | None = self._user_repo.get_user_by_id(user_id)
        if not user:
            return UseCaseFailureOutput(
                type="user", message=FailureType.NOT_FOUND_ERROR
            )
        if user.is_out:
            return UseCaseFailureOutput(
                type="Invalid user: user is out", message=FailureType.UNAUTHORIZED_ERROR
            )
        if not user.is_available:
            return UseCaseFailureOutput(
                type="Invalid user: user is not available",
                message=FailureType.UNAUTHORIZED_ERROR,
            )

        # Update token
        try:
            self._auth_repo.update_jwt(user_id)
        except UpdateFailErrorException as e:
            return UseCaseFailureOutput(
                type=f"{e.type_.get('msg')}",
                message=FailureType.INTERNAL_ERROR,
                code=HTTPStatus.INTERNAL_SERVER_ERROR,
            )

        new_token_info = self._auth_repo.get_jwt(user_id)

        # 최근접속 일자 갱신
        self._user_repo.update_current_connection_time(user_id)

        result = jsonify(access_token=new_token_info.access_token)
        return UseCaseSuccessOutput(value=result)

    def _is_valid_refresh_token(self, user_id: int) -> bool:
        token_info: JwtEntity | None = self._auth_repo.get_jwt(user_id)

        if not token_info or not token_info.refresh_token:
            return False
        try:
            decode_token(encoded_token=token_info.refresh_token)
        except PyJWTError:
            return False
        return True
