from __future__ import annotations

from functools import wraps
from logging import Logger
from logging import getLogger
from types import TracebackType
from typing import TYPE_CHECKING

from requests import Response

from qbittorrentapi import Version
from qbittorrentapi.definitions import APIKwargsT
from qbittorrentapi.definitions import APINames
from qbittorrentapi.definitions import ClientCache
from qbittorrentapi.exceptions import LoginFailed
from qbittorrentapi.exceptions import UnsupportedQbittorrentVersion
from qbittorrentapi.request import Request

if TYPE_CHECKING:
    from qbittorrentapi.client import Client

logger: Logger = getLogger(__name__)


class AuthAPIMixIn(Request):
    """
    Implementation of all ``Authorization`` API methods.

    :Usage:
        >>> from qbittorrentapi import Client
        >>> client = Client(host="localhost:8080", username="admin", password="adminadmin")
        >>> _ = client.is_logged_in
        >>> client.auth_log_in(username="admin", password="adminadmin")
        >>> client.auth_log_out()
    """

    @property
    def auth(self) -> Authorization:
        """Allows for transparent interaction with Authorization endpoints."""
        if self._authorization is None:
            self._authorization = Authorization(client=self)
        return self._authorization

    authorization = auth

    @property
    def is_logged_in(self) -> bool:
        """
        Returns True if low-overhead API call succeeds; False otherwise.

        There isn't a reliable way to know if an existing session is still valid without
        attempting to use it. qBittorrent invalidates cookies when they expire.

        :returns: ``True``/``False`` if current authorization cookie is accepted by qBittorrent
        """
        try:
            # use _request_manager() directly so log in is not attempted
            self._request_manager(
                http_method="post",
                api_namespace=APINames.Application,
                api_method="version",
            )
        except Exception:
            return False
        else:
            return True

    def auth_log_in(
        self,
        username: str | None = None,
        password: str | None = None,
        **kwargs: APIKwargsT,
    ) -> None:
        """
        Log in to qBittorrent host.

        :raises LoginFailed: if credentials failed to log in
        :raises Forbidden403Error: if user is banned...or not logged in

        :param username: username for qBittorrent client
        :param password: password for qBittorrent client
        """
        if username:
            self.username = username
            self._password = password or ""

        # trigger a (re-)initialization in case this is a new instance of qBittorrent
        self._initialize_context()

        creds = {"username": self.username, "password": self._password}
        auth_response = self._post_cast(
            _name=APINames.Authorization,
            _method="login",
            data=creds,
            response_class=Response,
            **kwargs,
        )

        if auth_response.text != "Ok.":
            logger.debug("Login failed")
            raise LoginFailed()
        logger.debug("Login successful")

        # check if the connected qBittorrent is fully supported by this Client yet
        if self._RAISE_UNSUPPORTEDVERSIONERROR:
            app_version = self.app_version()  # type: ignore
            api_version = self.app_web_api_version()  # type: ignore
            if not (
                Version.is_api_version_supported(api_version)
                and Version.is_app_version_supported(app_version)
            ):
                raise UnsupportedQbittorrentVersion(
                    "This version of qBittorrent is not fully supported => "
                    f"App Version: {app_version} API Version: {api_version}"
                )

    @property
    def _SID(self) -> str | None:
        """
        Authorization session cookie from qBittorrent using default cookie name `SID`.

        Backwards compatible for :meth:`~AuthAPIMixIn._session_cookie`.
        """
        return self._session_cookie()

    def _session_cookie(self, cookie_name: str = "SID") -> str | None:
        """
        Authorization session cookie from qBittorrent.

        :param cookie_name: Name of the authorization cookie; configurable after v4.5.0.
        """
        if self._http_session:
            return self._http_session.cookies.get(cookie_name, None)  # type: ignore
        return None

    def auth_log_out(self, **kwargs: APIKwargsT) -> None:
        """End session with qBittorrent."""
        self._post(_name=APINames.Authorization, _method="logout", **kwargs)

    def __enter__(self) -> Client:
        self.auth_log_in()
        return self  # type: ignore[return-value]

    def __exit__(
        self,
        exctype: type[BaseException] | None,
        excinst: BaseException | None,
        exctb: TracebackType | None,
    ) -> None:
        self.auth_log_out()


class Authorization(ClientCache[AuthAPIMixIn]):
    """
    Allows interaction with the ``Authorization`` API endpoints.

    :Usage:
        >>> from qbittorrentapi import Client
        >>> client = Client(host="localhost:8080", username="admin", password="adminadmin")
        >>> is_logged_in = client.auth.is_logged_in
        >>> client.auth.log_in(username="admin", password="adminadmin")
        >>> client.auth.log_out()
    """

    @property
    @wraps(AuthAPIMixIn.is_logged_in)
    def is_logged_in(self) -> bool:
        return self._client.is_logged_in

    @wraps(AuthAPIMixIn.auth_log_in)
    def log_in(
        self,
        username: str | None = None,
        password: str | None = None,
        **kwargs: APIKwargsT,
    ) -> None:
        return self._client.auth_log_in(username=username, password=password, **kwargs)

    @wraps(AuthAPIMixIn.auth_log_out)
    def log_out(self, **kwargs: APIKwargsT) -> None:
        return self._client.auth_log_out(**kwargs)
