"""Shared ESI client."""

# Standard Library
import logging
import random
from contextlib import contextmanager
from http import HTTPStatus

# Third Party
from aiopenapi3 import RequestError
from celery import Task

# Django
from django.utils import timezone

# Alliance Auth
from allianceauth.services.hooks import get_extension_logger
from esi.exceptions import (
    ESIBucketLimitException,
    ESIErrorLimitException,
    HTTPServerError,
)
from esi.openapi_clients import ESIClientProvider

# AA Belt Radar
from beltradar import (
    __app_name_useragent__,
    __esi_compatibility_date__,
    __github_url__,
    __operations__,
    __title__,
    __version__,
)
from beltradar.errors import DownTimeError

esi = ESIClientProvider(
    compatibility_date=__esi_compatibility_date__,
    ua_appname=__app_name_useragent__,
    ua_version=__version__,
    ua_url=__github_url__,
    operations=__operations__,
)

DOWNTIME_TIMER = 60 * 10  # 10 minutes


class AppLogger(logging.LoggerAdapter):
    """
    Custom logger adapter that adds a prefix to log messages.

    Taken from the `allianceauth-app-utils` package.
    Credits to: Erik Kalkoken
    """

    def __init__(self, my_logger, prefix):
        """
        Initializes the AppLogger with a logger and a prefix.

        :param my_logger: Logger instance
        :type my_logger: logging.Logger
        :param prefix: Prefix string to add to log messages
        :type prefix: str
        """

        super().__init__(my_logger, {})

        self.prefix = prefix

    def process(self, msg, kwargs):
        """
        Prepares the log message by adding the prefix.

        :param msg: Original log message
        :type msg: str
        :param kwargs: Additional keyword arguments for logging
        :type kwargs: dict
        :return: Tuple of modified message and kwargs
        :rtype: tuple
        """
        return f"[{self.prefix}] {msg}", kwargs


logger = AppLogger(my_logger=get_extension_logger(__name__), prefix=__title__)


@contextmanager
def retry_task_on_esi_error(task: Task):
    """Retry Task when a ESI error occurs.

    Taken from the `allianceauth-app-utils` package.
    Credits to: Erik Kalkoken

    Retries on:
    - Error limits reached (ESIErrorLimitException)
    - Rate limit errors (ESIBucketLimitException)
    - HTTPError with status codes 502, 503, 504 (server errors)

    :param task: Celery Task instance
    :return: Context manager that retries the task on ESI errors.

    """

    def retry(exc: Exception, retry_after: float, issue: str):
        backoff_jitter = int(random.uniform(2, 5) ** task.request.retries)
        countdown = retry_after + backoff_jitter
        logger.warning(
            "ESI Error encountered: %s. Retrying after %.2f seconds. Issue: %s",
            str(exc),
            countdown,
            issue,
        )
        raise task.retry(countdown=countdown, exc=exc)

    def daily_downtime():
        """Checks if the current time is within ESI's daily downtime window (11:00 - 11:15 UTC)."""
        is_downtime = (
            timezone.now().time() >= timezone.datetime.strptime("11:00", "%H:%M").time()
            and timezone.now().time()
            <= timezone.datetime.strptime("11:15", "%H:%M").time()
        )
        if not is_downtime:
            return False
        return True

    try:
        if daily_downtime():
            raise DownTimeError("ESI is in daily downtime")
        yield
    except ESIErrorLimitException as exc:
        retry(exc, exc.reset, "ESI Error Limit Reached")
    except ESIBucketLimitException as exc:
        retry(exc, exc.reset, f"ESI Bucket Limit Reached for {exc.bucket}")
    except HTTPServerError as exc:
        if exc.status_code in [
            HTTPStatus.BAD_GATEWAY,
            HTTPStatus.SERVICE_UNAVAILABLE,
            HTTPStatus.GATEWAY_TIMEOUT,
        ]:
            retry(exc, DOWNTIME_TIMER, f"ESI seems to be down (HTTP {exc.status_code})")
        raise exc
    except RequestError as exc:
        retry(exc, DOWNTIME_TIMER, "Request Error")
    except DownTimeError as exc:
        retry(exc, DOWNTIME_TIMER, "Downtime Error")
