"""Auth mixin: OAuth app creation, token acquisition, authorization URL generation."""

import json
import logging
from urllib.parse import urlencode

from httpx import AsyncClient
from httpx import HTTPError

from minimal_activitypub import USER_AGENT
from minimal_activitypub import __display_name__
from minimal_activitypub.constants import REDIRECT_URI
from minimal_activitypub.exceptions import NetworkError
from minimal_activitypub.utils import _check_exception

logger = logging.getLogger(__display_name__)


class _AuthMixin:
    """Mixin providing static OAuth/authentication methods."""

    @staticmethod
    async def generate_authorization_url(
        instance_url: str,
        client_id: str,
        user_agent: str = USER_AGENT,
    ) -> str:
        """Create URL to get access token interactively from website.

        :param instance_url: The URL of the Mastodon instance you want to connect to
        :param client_id: Client id of app as generated by create_app method
        :param user_agent: User agent identifier to use. Defaults to minimal_activitypub related one.

        :returns: String containing URL to visit to get access token interactively from instance.
        """
        logger.debug(
            "ActivityPub.get_auth_token_interactive(instance_url=%s, client=...,client_id=%s, user_agent=%s)",
            instance_url,
            client_id,
            user_agent,
        )
        if "http" not in instance_url:
            instance_url = f"https://{instance_url}"

        url_params = urlencode(
            {
                "response_type": "code",
                "client_id": client_id,
                "redirect_uri": REDIRECT_URI,
                "scope": "read write",
            }
        )
        auth_url = f"{instance_url}/oauth/authorize?{url_params}"
        logger.debug(
            "ActivityPub.get_auth_token_interactive(...) -> %s",
            auth_url,
        )

        return auth_url

    @staticmethod
    async def validate_authorization_code(
        client: AsyncClient,
        instance_url: str,
        authorization_code: str,
        client_id: str,
        client_secret: str,
    ) -> str:
        """Validate an authorization code and get access token needed for API access.

        :param client: httpx.AsyncClient
        :param instance_url: The URL of the Mastodon instance you want to connect to
        :param authorization_code: authorization code
        :param client_id: client id as returned by create_app method
        :param client_secret: client secret as returned by create_app method

        :returns: access token
        """
        logger.debug(
            "ActivityPub.validate_authorization_code(authorization_code=%s, client_id=%s, client_secret=<redacted>)",
            authorization_code,
            client_id,
        )
        if "http" not in instance_url:
            instance_url = f"https://{instance_url}"

        data = {
            "client_id": client_id,
            "client_secret": client_secret,
            "scope": "read write",
            "redirect_uri": REDIRECT_URI,
            "grant_type": "authorization_code",
            "code": authorization_code,
        }
        try:
            response = await client.post(url=f"{instance_url}/oauth/token", data=data)
            logger.debug(
                "ActivityPub.validate_authorization_code - response:\n%s",
                response,
            )
            await _check_exception(response)
            response_dict = response.json()
        except HTTPError as error:
            raise NetworkError from error

        logger.debug(
            "ActivityPub.validate_authorization_code - response.json: \n%s",
            json.dumps(response_dict, indent=4),
        )
        return str(response_dict["access_token"])

    @staticmethod
    async def get_auth_token(  # noqa: PLR0913  - No way around needing all this parameters
        instance_url: str,
        username: str,
        password: str,
        client: AsyncClient,
        user_agent: str = USER_AGENT,
        client_website: str = "https://pypi.org/project/minimal-activitypub/",
    ) -> str:
        """Create an app and use it to get an access token.

        :param instance_url: The URL of the Mastodon instance you want to connect to
        :param username: The username of the account you want to get an auth_token for
        :param password: The password of the account you want to get an auth_token for
        :param client: httpx.AsyncClient
        :param user_agent: User agent identifier to use. Defaults to minimal_activitypub related one.
        :param client_website: Link to site for user_agent. Defaults to link to minimal_activitypub on Pypi.org

        :returns: The access token is being returned.
        """
        logger.debug(
            "ActivityPub.get_auth_token(instance_url=%s, username=%s, password=<redacted>, client=..., "
            "user_agent=%s, client_website=%s)",
            instance_url,
            username,
            user_agent,
            client_website,
        )
        if "http" not in instance_url:
            instance_url = f"https://{instance_url}"

        client_id, client_secret = await _AuthMixin.create_app(
            client_website=client_website,
            instance_url=instance_url,
            client=client,
            user_agent=user_agent,
        )

        data = {
            "client_id": client_id,
            "client_secret": client_secret,
            "scope": "read write",
            "redirect_uris": REDIRECT_URI,
            "grant_type": "password",
            "username": username,
            "password": password,
        }

        try:
            response = await client.post(url=f"{instance_url}/oauth/token", data=data)
            logger.debug("ActivityPub.get_auth_token - response:\n%s", response)
            await _check_exception(response)
            response_dict = response.json()
        except HTTPError as error:
            raise NetworkError from error

        logger.debug(
            "ActivityPub.get_auth_token - response.json: \n%s",
            json.dumps(response_dict, indent=4),
        )
        return str(response_dict["access_token"])

    @staticmethod
    async def create_app(
        instance_url: str,
        client: AsyncClient,
        user_agent: str = USER_AGENT,
        client_website: str = "https://pypi.org/project/minimal-activitypub/",
    ) -> tuple[str, str]:
        """Create an app.

        :param instance_url: The URL of the Mastodon instance you want to connect to
        :param client: httpx.AsyncClient
        :param user_agent: User agent identifier to use. Defaults to minimal_activitypub related one.
        :param client_website: Link to site for user_agent. Defaults to link to minimal_activitypub on Pypi.org

        :returns: tuple(client_id, client_secret)
        """
        logger.debug(
            "ActivityPub.create_app(instance_url=%s, client=..., user_agent=%s, client_website=%s)",
            instance_url,
            user_agent,
            client_website,
        )

        if "http" not in instance_url:
            instance_url = f"https://{instance_url}"

        data = {
            "client_name": user_agent,
            "client_website": client_website,
            "scopes": "read write",
            "redirect_uris": REDIRECT_URI,
        }
        try:
            response = await client.post(url=f"{instance_url}/api/v1/apps", data=data)
            logger.debug("ActivityPub.create_app response: \n%s", response)
            await _check_exception(response)
            response_dict = response.json()
        except HTTPError as error:
            raise NetworkError from error

        logger.debug(
            "ActivityPub.create_app response.json: \n%s",
            json.dumps(response_dict, indent=4),
        )
        return (response_dict["client_id"]), (response_dict["client_secret"])
