"""
ExactTarget OAuth support.
Support Authentication from IMH using JWT token and pre-shared key.
Requires package pyjwt
"""

from __future__ import annotations

from datetime import datetime, timedelta, timezone
from typing import Any

import jwt

from social_core.exceptions import AuthCanceled, AuthFailed

from .oauth import BaseOAuth2


class ExactTargetOAuth2(BaseOAuth2):
    name = "exacttarget"

    def get_user_details(self, response):
        """Use the email address of the user, suffixed by _et"""
        user = response.get("token", {}).get("request", {}).get("user", {})
        if "email" in user:
            user["username"] = user["email"]
        return user

    def get_user_id(self, details, response):
        """
        Create a user ID from the ET user ID. Uses details rather than the
        default response, as only the token is available in response. details
        is much richer:
            {
                'expiresIn': 1200,
                'username': 'example@example.com',
                'refreshToken': '1234567890abcdef',
                'internalOauthToken': 'jwttoken.......',
                'oauthToken': 'yetanothertoken',
                'id': 123456,
                'culture': 'en-US',
                'timezone': {
                    'shortName': 'CST',
                    'offset': -6.0,
                    'dst': False,
                    'longName': '(GMT-06:00) Central Time (No Daylight Saving)'
                },
                'email': 'example@example.com'
            }
        """
        return str(details.get("id"))

    def uses_redirect(self) -> bool:
        return False

    def auth_url(self) -> str:
        return ""

    def process_error(self, data) -> None:
        if data.get("error"):
            error = self.data.get("error_description") or self.data["error"]
            raise AuthFailed(self, error)

    def do_auth(self, token, *args, **kwargs):
        _key, secret = self.get_key_and_secret()
        try:  # Decode the token, using the Application Signature from settings
            decoded = jwt.decode(token, secret, algorithms=["HS256"])
        except jwt.DecodeError as error:
            # Wrong signature, fail authentication
            raise AuthCanceled(self) from error
        kwargs.update({"response": {"token": decoded}, "backend": self})
        return self.strategy.authenticate(*args, **kwargs)

    def auth_complete(self, *args, **kwargs):
        """Completes login process, must return user instance"""
        token = self.data.get("jwt", {})
        if not token:
            raise AuthFailed(self, "Authentication Failed")
        return self.do_auth(token, *args, **kwargs)

    def extra_data(
        self,
        user,
        uid: str,
        response: dict[str, Any],
        details: dict[str, Any],
        pipeline_kwargs: dict[str, Any],
    ) -> dict[str, Any]:
        """Load extra details from the JWT token"""
        data = {
            "id": details.get("id"),
            "email": details.get("email"),
            # OAuth token, for use with legacy SOAP API calls:
            #   http://bit.ly/13pRHfo
            "internalOauthToken": details.get("internalOauthToken"),
            # Token for use with the Application ClientID for the FUEL API
            "oauthToken": details.get("oauthToken"),
            # If the token has expired, use the FUEL API to get a new token see
            # http://bit.ly/10v1K5l and http://bit.ly/11IbI6F - set legacy=1
            "refreshToken": details.get("refreshToken"),
        }

        # The expiresIn value determines how long the tokens are valid for.
        # Take a bit off, then convert to an int timestamp
        expiresSeconds = details.get("expiresIn", 0) - 30
        expires = datetime.now(timezone.utc) + timedelta(seconds=expiresSeconds)
        data["expires"] = (expires - datetime(1970, 1, 1)).total_seconds()

        if response.get("token"):
            token = response["token"]
            org = token.get("request", {}).get("organization")
            if org:
                data["stack"] = org.get("stackKey")
                data["enterpriseId"] = org.get("enterpriseId")
        return data
