"""
OAuth2 Adapter for Battle.net

Resources:

* Battle.net OAuth2 documentation:
    https://dev.battle.net/docs/read/oauth
* Battle.net API documentation:
    https://dev.battle.net/io-docs
* Original announcement:
    https://us.battle.net/en/forum/topic/13979297799
* The Battle.net API forum:
    https://us.battle.net/en/forum/15051532/
"""

from http import HTTPStatus

from django.conf import settings

from allauth.socialaccount.adapter import get_adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Error
from allauth.socialaccount.providers.oauth2.views import (
    OAuth2Adapter,
    OAuth2CallbackView,
    OAuth2LoginView,
)


class Region:
    APAC = "apac"
    CN = "cn"
    EU = "eu"
    KR = "kr"
    SEA = "sea"
    TW = "tw"
    US = "us"


def _check_errors(response):
    try:
        data = response.json()
    except ValueError:  # JSONDecodeError on py3
        raise OAuth2Error(f"Invalid JSON from Battle.net API: {response.text!r}")

    if response.status_code >= HTTPStatus.BAD_REQUEST or "error" in data:
        # For errors, we expect the following format:
        # {"error": "error_name", "error_description": "Oops!"}
        # For example, if the token is not valid, we will get:
        # {
        #   "error": "invalid_token",
        #   "error_description": "Invalid access token: abcdef123456"
        # }
        # For the profile API, this may also look like the following:
        # {"code": 403, "type": "Forbidden", "detail": "Account Inactive"}
        error = data.get("error", "") or data.get("type", "")
        desc = data.get("error_description", "") or data.get("detail", "")

        raise OAuth2Error(f"Battle.net error: {error} ({desc})")

    # The expected output from the API follows this format:
    # {"id": 12345, "battletag": "Example#12345"}
    # The battletag is optional.
    if "id" not in data:
        # If the id is not present, the output is not usable (no UID)
        raise OAuth2Error(f"Invalid data from Battle.net API: {data!r}")

    return data


class BattleNetOAuth2Adapter(OAuth2Adapter):
    """
    OAuth2 adapter for Battle.net
    https://dev.battle.net/docs/read/oauth

    Region is set to us by default, but can be overridden with the
    `region` GET parameter when performing a login.
    Can be any of eu, us, kr, sea, tw or cn
    """

    provider_id = "battlenet"

    valid_regions = (
        Region.APAC,
        Region.CN,
        Region.EU,
        Region.KR,
        Region.SEA,
        Region.TW,
        Region.US,
    )

    @property
    def battlenet_region(self):
        # Check by URI query parameter first.
        region = self.request.GET.get("region", "").lower()
        if region == Region.SEA:
            # South-East Asia uses the same region as US everywhere
            return Region.US
        if region in self.valid_regions:
            return region

        # Second, check the provider settings.
        region = (
            getattr(settings, "SOCIALACCOUNT_PROVIDERS", {})
            .get("battlenet", {})
            .get("REGION", "us")
        )

        if region in self.valid_regions:
            return region

        return Region.US

    @property
    def battlenet_base_url(self):
        region = self.battlenet_region
        if region == Region.CN:
            return "https://oauth.battlenet.com.cn"
        return "https://oauth.battle.net"

    @property
    def access_token_url(self):
        return f"{self.battlenet_base_url}/token"

    @property
    def authorize_url(self):
        return f"{self.battlenet_base_url}/authorize"

    @property
    def profile_url(self):
        return f"{self.battlenet_base_url}/userinfo"

    def complete_login(self, request, app, token, **kwargs):
        headers = {"authorization": f"Bearer {token.token}"}
        with get_adapter().get_requests_session() as sess:
            response = sess.get(self.profile_url, headers=headers)
            data = _check_errors(response)

        # Add the region to the data so that we can have it in `extra_data`.
        data["region"] = self.battlenet_region

        return self.get_provider().sociallogin_from_response(request, data)

    def get_callback_url(self, request, app):
        r = super(BattleNetOAuth2Adapter, self).get_callback_url(request, app)
        region = request.GET.get("region", "").lower()
        # Pass the region down to the callback URL if we specified it
        if region and region in self.valid_regions:
            r += f"?region={region}"
        return r


oauth2_login = OAuth2LoginView.adapter_view(BattleNetOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(BattleNetOAuth2Adapter)
