import functools
from typing import Text, Tuple, Optional

from flask import render_template, session, g

from everyclass.common.flask import plugin_available
from everyclass.rpc import RpcTimeout, RpcResourceNotFound, RpcBadRequest, RpcClientException, RpcServerNotAvailable, RpcServerException
from everyclass.rpc.entity import StudentTimetableResult
from everyclass.server import sentry, logger
from everyclass.server.user import service as user_service
from everyclass.server.user.exceptions import AlreadyRegisteredError, InvalidTokenError
from everyclass.server.utils.config import get_config
from everyclass.server.utils.web_consts import MSG_400, SESSION_CURRENT_USER, MSG_NOT_LOGGED_IN


def disallow_in_maintenance(func):
    """
    a decorator for routes which should be unavailable in maintenance mode.
    """

    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        config = get_config()
        if config.MAINTENANCE:
            return render_template('maintenance.html')
        return func(*args, **kwargs)

    return wrapped


def url_semester_check(func):
    """
    捕获 URL 中异常的 semester 值
    """

    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        if len(kwargs["url_semester"]) > 11:
            return render_template('common/error.html', message=MSG_400)
        return func(*args, **kwargs)

    return wrapped


def login_required(func):
    """
    a decorator for routes which is only available for logged-in users.
    """

    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        if not session.get(SESSION_CURRENT_USER, None):
            return render_template('common/error.html', message=MSG_NOT_LOGGED_IN, action="login")
        return func(*args, **kwargs)

    return wrapped


def _error_page(message: str, sentry_capture: bool = False, log: str = None):
    """return a error page with a message. if sentry is available, tell user that they can report the problem."""
    sentry_param = {}
    if sentry_capture and plugin_available("sentry"):
        sentry.captureException()
        sentry_param.update({"event_id": g.sentry_event_id,
                             "public_dsn": sentry.client.get_public_dsn('https')
                             })
    if log:
        logger.info(log)
    return render_template('common/error.html', message=message, **sentry_param)


def handle_exception_with_error_page(e: Exception) -> Text:
    """处理抛出的异常,返回错误页。
    """
    from everyclass.server.utils.web_consts import MSG_TIMEOUT, MSG_404, MSG_400, MSG_INTERNAL_ERROR, MSG_503, MSG_ALREADY_REGISTERED, \
        MSG_TOKEN_INVALID

    if isinstance(e, AlreadyRegisteredError):
        return _error_page(MSG_ALREADY_REGISTERED)
    if isinstance(e, InvalidTokenError):
        return _error_page(MSG_TOKEN_INVALID)

    if isinstance(e, RpcTimeout):
        return _error_page(MSG_TIMEOUT, sentry_capture=True)
    elif isinstance(e, RpcResourceNotFound):
        return _error_page(MSG_404, sentry_capture=True)
    elif isinstance(e, RpcBadRequest):
        return _error_page(MSG_400,
                           log="Got bad request, upstream returned status code {} with message {}.".format(*e.args),
                           sentry_capture=True)
    elif isinstance(e, RpcClientException):
        return _error_page(MSG_400, sentry_capture=True)
    elif isinstance(e, RpcServerNotAvailable):
        return _error_page(MSG_503, sentry_capture=True)
    elif isinstance(e, RpcServerException):
        return _error_page(MSG_INTERNAL_ERROR, sentry_capture=True)
    else:
        return _error_page(MSG_INTERNAL_ERROR, sentry_capture=True)


def check_permission(student: StudentTimetableResult) -> Tuple[bool, Optional[str]]:
    """
    检查当前登录的用户是否有权限访问此学生

    :param student: 被访问的学生
    :return: 第一个返回值为布尔类型,True 标识可以访问,False 表示没有权限访问。第二个返回值为没有权限访问时需要返回的模板
    """
    can, reason = user_service.has_access(student.student_id,
                                          session.get(SESSION_CURRENT_USER).identifier if session.get(SESSION_CURRENT_USER, None) else None)
    if reason == user_service.REASON_LOGIN_REQUIRED:
        return False, render_template('entity/studentBlocked.html',
                                      name=student.name,
                                      falculty=student.deputy,
                                      class_name=student.klass,
                                      level=1)
    if reason == user_service.REASON_PERMISSION_ADJUST_REQUIRED:
        return False, render_template('entity/studentBlocked.html',
                                      name=student.name,
                                      falculty=student.deputy,
                                      class_name=student.klass,
                                      level=3)
    if not can:
        return False, render_template('entity/studentBlocked.html',
                                      name=student.name,
                                      falculty=student.deputy,
                                      class_name=student.klass,
                                      level=2)

    return True, None