import jwt

from datetime import timedelta
from django.conf import settings
from django.utils import timezone
from django.contrib.auth import get_user_model
from functools import wraps
from django.http import HttpResponseForbidden
from django.contrib.auth import login


class TokenError(Exception):
    msg = ''

    def __str__(self):
        return '%s' % self.msg


class TokenDecodeError(TokenError):
    msg = 'token decode error'


class TokenExpiredError(TokenError):
    msg = 'token has expired'


class TokenUserNotFound(TokenError):
    msg = 'user not Found'


class TokenAuthModelNotFound(TokenError):
    msg = 'auth model not found'


class TokenBackend:

    _settings = {
        "ACCESS_TOKEN_LIFETIME": timedelta(minutes=5),  # 认证TOKEN有效时间
        "UPDATE_USER_LAST_LOGIN_TIME": True,  # 是否更新USER最后登录时间

        # 签名所使用的算法
        # 包含以下选项
        # [
        #  'HS256', 'HS384', 'HS512', 'ES256', 'ES384', 'ES512',
        #  'RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512',
        #  'EdDSA'
        # ]
        # 除HS系列外，需要额外安装 cryptography 依赖(可选：使用pyjwt[crypto]替代cryptography)
        "ALGORITHM": "HS256",
        "SIGING_KEY": settings.SECRET_KEY,  # 加密私钥 默认使用django的SECRET_KEY
        "VERIFYING_KEY": None,  # 验证密钥 HS算法下，将不会使用该值。 其余算法模式下，此处应填写公钥
        "AUDIENCE": None,
        "ISSUER": None,

        "AUTH_HEADER_TYPES": ['Bearer', ],
        "AUTH_HEADER_NAME": "AUTHORIZATION",
        "USER_PRIMARY_KEY_FIELD_NAME": "id",
        "USER_PRIMARY_KEY_FIELD_CLAIM": "id",
        "LAST_LOGIN_TIME_NAME": 'last_login',
    }

    def __init__(self, *args, **kwargs):
        if hasattr(settings, 'DJANGO_JWT_SETTINGS'):
            self._settings = dict(
                self._settings, **settigns.DJANGO_JWT_SETTINGS)
        super().__init__(*args, **kwargs)

    def __getattr__(self, key):
        return self._settings.get(key)

    def __setattr__(self, key, value):
        self._settings[key] = value

    def get_payload(self, user):
        payload = {
            "expired": int((timezone.now() + self.ACCESS_TOKEN_LIFETIME).timestamp() * 1000),
            "token_type": "access",
        }
        payload[self.USER_PRIMARY_KEY_FIELD_CLAIM] = getattr(
            user, self.USER_PRIMARY_KEY_FIELD_NAME)
        return payload

    def encode(self, payload):
        if not payload.get('aud') and self.AUDIENCE:
            payload['aud'] = self.AUDIENCE
        if not payload.get('iss') and self.ISSUER:
            payload['iss'] = self.ISSUER
        return jwt.encode(payload=payload, key=self.SIGING_KEY, algorithm=self.ALGORITHM)

    def decode(self, raw_token):
        params = {
            "jwt": raw_token,
            "key": self.SIGING_KEY if 'HS' in self.ALGORITHM else self.VERIFYING_KEY,
            "algorithms": [self.ALGORITHM],
        }

        return jwt.decode(**params)

    def access_token(self, user):
        token = self.encode(self.get_payload(user))
        if self.LAST_LOGIN_TIME_NAME:
            user.last_login = timezone.now()
            user.save()
        return token

    def verify(self, raw_token):
        try:
            payload = self.decode(raw_token)
        except:
            raise TokenDecodeError()
        if timezone.now().timestamp() > payload['expired']:
            raise TokenExpiredError()
        User = get_user_model()
        query_params = {}
        query_params[self.USER_PRIMARY_KEY_FIELD_NAME] = payload[self.USER_PRIMARY_KEY_FIELD_CLAIM]
        try:
            user = User.objects.get(**query_params)
        except:
            raise TokenUserNotFound()
        return payload, user


Token = TokenBackend()


def JsonWebTokenAuthentication():

    def decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            authentication = request.headers.get(Token.AUTH_HEADER_NAME, '')
            if not any(map(lambda item: item in authentication, Token.AUTH_HEADER_TYPES)):
                return HttpResponseForbidden()
            try:
                token = authentication.split(' ')[1]
            except IndexError:
                return HttpResponseForbidden()
            try:
                payload, user = Token.verify(token)
            except TokenError:
                return HttpResponseForbidden()
            request.user = user
            request.jwt_payload = payload
            return view_func(request, *args, **kwargs)
        return _wrapped_view
    return decorator
