import datetime
from typing import Any

from pandas import DataFrame

from . import _consts, _util
from ._apiclient.new import AuthenticatedClient
from ._apiclient.new.api.v1 import v1_accountinfo_retrieve
from ._errors import CasaGeoError, InsufficientCreditsError
from ._types import CasaGeoResult


class AccountResult(CasaGeoResult):
    """
    Represents the result of an account lookup operation.
    """

    def username(self) -> str:
        """Return the username associated with the account."""
        return self._data.get("user", "")

    def accounttype(self) -> str:
        """Return a string describing the type of the account."""
        return self._data.get("note", "")

    def credits(self) -> int:
        """Return the number of available credits."""
        return self._data.get("credits", 0)

    def expires(self) -> datetime.datetime | None:
        """Return the expiration date and time of the account."""
        try:
            return datetime.datetime.fromisoformat(self._data["expires"])
        except (KeyError, ValueError):
            return None

    def dataframe(self, id_: Any | None = None) -> DataFrame:
        """
        Return the result as a DataFrame.

        The dataframe will contain the following columns:

        - *id* (Any): Fixed identifier added to each row. Can be
          overridden using the ``id_`` parameter.
        - *subid* (int): Numeric index of the result value starting from zero.
        - *username* (str): Username associated with the account.
        - *account_type* (str): Type of the account.
        - *credits* (int): Number of available credits.
        - *expires* (datetime): Expiration date and time of the account.

        Args:
            id_: Fixed identifier to be added to each row. Defaults to 1.

        Returns:
            DataFrame: Account information as a DataFrame.
        """
        if id_ is None:
            id_ = 1

        data: list[dict] = []
        for index, account in enumerate([self._data]):
            data.append(row := {})

            if True:
                # fmt: off
                row["id"]           = id_
                row["subid"]        = index
                row["username"]     = account.get("user")
                row["accounttype"]  = account.get("note")
                row["credits"]      = account.get("credits")
                row["expires"]      = _util.fromisoformat_or_null(account.get("expires"))
                # fmt: on

        return DataFrame(data)


class CasaGeoClient:
    """
    The casaGeo API client.

    The ``preferred_*`` attributes are used as default values by
    CasaGeoCoder, CasaGeoSpatial, etc. when making API requests.

    Attributes:
        preferred_language:
            The preferred language for responses. This must be a valid IETF
            BCP47 language tag, such as "en-US", or a comma-separated list of
            such tags in order of preference.

        preferred_political_view:
            The preferred political view for responses. This must be a valid ISO
            3166-1 alpha-3 country code.

        preferred_unit_system:
            The preferred unit system for responses, either ``"metric"`` or
            ``"imperial"``.

    Parameters:
        key: Your casaGeo API license key.
    """

    def __init__(
        self,
        key: str,
        *,
        preferred_language: str | None = None,
        preferred_political_view: str | None = None,
        preferred_unit_system: str | None = None,
    ):
        server = _consts.SERVER

        self._client = AuthenticatedClient(base_url=server, token=key, prefix="")

        self.preferred_language = preferred_language
        self.preferred_political_view = preferred_political_view
        self.preferred_unit_system = preferred_unit_system

    @AccountResult.wrap_errors()
    def account_info(self) -> AccountResult:
        """
        Query the server for account information.

        Returns:
            AccountResult: An AccountResult instance.
        """

        return AccountResult.from_response(
            v1_accountinfo_retrieve.sync_detailed(client=self._client)
        )

    def ensure_enough_credits(self, required: int) -> None:
        """
        Ensure that the account has enough credits to make a request.

        Parameters:
            required: The number of credits required.

        Raises:
            CasaGeoError: If account information could not be retrieved.
            InsufficientCreditsError: If the account does not have enough credits.
        """
        if not (info := self.account_info()):
            raise CasaGeoError(
                "Failed to retrieve account information"
            ) from info.error()
        if (available := info.credits()) < required:
            raise InsufficientCreditsError(required=required, available=available)
