from __future__ import annotations

import json
import logging
import uuid
from datetime import datetime, timedelta, timezone
from enum import Enum
from typing import Any

import jwt

from .api_common import ApiCommon
from .const import HEADER_ACCESS_TOKEN, HEADER_AUGUST_ACCESS_TOKEN
from .time import parse_datetime

# The default time before expiration to refresh a token
DEFAULT_RENEWAL_THRESHOLD = timedelta(days=7)

_LOGGER = logging.getLogger(__name__)


def to_authentication_json(authentication):
    if authentication is None:
        return json.dumps({})

    return json.dumps(
        {
            "install_id": authentication.install_id,
            "access_token": authentication.access_token,
            "access_token_expires": authentication.access_token_expires,
            "state": authentication.state.value,
        }
    )


def from_authentication_json(data):
    if data is None:
        return None

    install_id = data["install_id"]
    access_token = data["access_token"]
    access_token_expires = data["access_token_expires"]
    state = AuthenticationState(data["state"])
    return Authentication(state, install_id, access_token, access_token_expires)


class Authentication:
    def __init__(
        self, state, install_id=None, access_token=None, access_token_expires=None
    ):
        self._state = state
        self._install_id = str(uuid.uuid4()) if install_id is None else install_id
        self._access_token = access_token
        self._access_token_expires = access_token_expires
        self._parsed_expiration_time = None
        if access_token_expires:
            self._parsed_expiration_time = parse_datetime(access_token_expires)

    @property
    def install_id(self):
        return self._install_id

    @property
    def access_token(self):
        return self._access_token

    @property
    def access_token_expires(self):
        return self._access_token_expires

    @property
    def state(self):
        return self._state

    @state.setter
    def state(self, value):
        self._state = value

    def parsed_expiration_time(self):
        return self._parsed_expiration_time

    def is_expired(self):
        return self._parsed_expiration_time < datetime.now(timezone.utc)


class AuthenticationState(Enum):
    REQUIRES_AUTHENTICATION = "requires_authentication"
    REQUIRES_VALIDATION = "requires_validation"
    AUTHENTICATED = "authenticated"
    BAD_PASSWORD = "bad_password"  # nosec


class ValidationResult(Enum):
    VALIDATED = "validated"
    INVALID_VERIFICATION_CODE = "invalid_verification_code"


class AuthenticatorCommon:
    def __init__(
        self,
        api: ApiCommon,
        login_method: str | None,
        username: str | None,
        password: str | None,
        install_id: str | None = None,
        access_token_cache_file: str | None = None,
        access_token_renewal_threshold: timedelta = DEFAULT_RENEWAL_THRESHOLD,
    ) -> None:
        self._api = api
        self._login_method = login_method
        self._username = username
        self._password = password
        self._install_id = install_id
        self._access_token_cache_file = access_token_cache_file
        self._access_token_renewal_threshold = access_token_renewal_threshold
        self._authentication = None

    def _authentication_from_session_response(
        self,
        install_id: str,
        response_headers: dict[str, Any],
        json_dict: dict[str, Any],
    ) -> Authentication:
        access_token = (
            response_headers.get(HEADER_ACCESS_TOKEN)
            or response_headers[HEADER_AUGUST_ACCESS_TOKEN]
        )
        access_token_expires = json_dict["expiresAt"]
        v_password = json_dict["vPassword"]
        v_install_id = json_dict["vInstallId"]

        if not v_password:
            state = AuthenticationState.BAD_PASSWORD
        elif not v_install_id:
            state = AuthenticationState.REQUIRES_VALIDATION
        else:
            state = AuthenticationState.AUTHENTICATED

        self._authentication = Authentication(
            state, install_id, access_token, access_token_expires
        )

        return self._authentication

    def should_refresh(self):
        return self._authentication.state == AuthenticationState.AUTHENTICATED and (
            (self._authentication.parsed_expiration_time() - datetime.now(timezone.utc))
            < self._access_token_renewal_threshold
        )

    def _process_refreshed_access_token(self, refreshed_token):
        jwt_claims = jwt.decode(refreshed_token, options={"verify_signature": False})

        if "exp" not in jwt_claims:
            _LOGGER.warning("Did not find expected `exp' claim in JWT")
            return self._authentication

        new_expiration = datetime.utcfromtimestamp(jwt_claims["exp"])  # noqa: DTZ004
        # The yale access api always returns expiresAt in the format
        # '%Y-%m-%dT%H:%M:%S.%fZ'
        # from the get_session api call
        # It is important we store access_token_expires formatted
        # the same way for compatibility
        self._authentication = Authentication(
            self._authentication.state,
            install_id=self._authentication.install_id,
            access_token=refreshed_token,
            access_token_expires=new_expiration.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
        )

        _LOGGER.info("Successfully refreshed access token")
        return self._authentication
