from __future__ import annotations

import time
from typing import Iterable, Sequence

import requests

from .config import get_mal_client_id


class MalClient:
    base_url = "https://api.myanimelist.net/v2"

    def __init__(
        self,
        client_id: str,
        *,
        timeout_seconds: int = 60,
        sleep_seconds: float = 0.0,
        retry_attempts: int = 3,
        retry_backoff_seconds: float = 2.0,
        rate_limit_wait: float = 30.0,
    ) -> None:
        self.client_id = client_id
        self.timeout_seconds = timeout_seconds
        self.sleep_seconds = sleep_seconds
        self.retry_attempts = retry_attempts
        self.retry_backoff_seconds = retry_backoff_seconds
        self.rate_limit_wait = rate_limit_wait
        self.session = requests.Session()
        self.session.headers.update(
            {
                "X-MAL-CLIENT-ID": self.client_id,
                "User-Agent": "AniRec/2.0",
            }
        )

    @classmethod
    def from_env(
        cls,
        *,
        sleep_seconds: float = 0.0,
        timeout_seconds: int = 60,
        retry_attempts: int = 3,
        retry_backoff_seconds: float = 2.0,
        rate_limit_wait: float = 30.0,
    ) -> "MalClient":
        return cls(
            get_mal_client_id(),
            sleep_seconds=sleep_seconds,
            timeout_seconds=timeout_seconds,
            retry_attempts=retry_attempts,
            retry_backoff_seconds=retry_backoff_seconds,
            rate_limit_wait=rate_limit_wait,
        )

    def _format_fields(self, fields: Sequence[str] | str | None) -> str | None:
        if fields is None:
            return None
        if isinstance(fields, str):
            return fields
        return ",".join(fields)

    def _get_json(
        self,
        url: str,
        *,
        params: dict | None = None,
        allow_missing: bool = False,
    ) -> dict | None:
        while True:
            last_error: Exception | None = None
            for attempt in range(1, self.retry_attempts + 1):
                try:
                    response = self.session.get(url, params=params, timeout=self.timeout_seconds)
                    if allow_missing and response.status_code in (403, 404):
                        return None
                    if response.status_code == 429 or 500 <= response.status_code < 600:
                        last_error = requests.HTTPError(
                            f"HTTP {response.status_code} for {url}",
                            response=response,
                        )
                        if attempt < self.retry_attempts:
                            time.sleep(self.retry_backoff_seconds * attempt)
                            continue
                    response.raise_for_status()
                    if self.sleep_seconds:
                        time.sleep(self.sleep_seconds)
                    return response.json()
                except (requests.Timeout, requests.ConnectionError, requests.HTTPError, ValueError) as exc:
                    last_error = exc
                    if allow_missing and isinstance(exc, requests.HTTPError) and getattr(exc, "response", None) is not None:
                        if exc.response.status_code in (403, 404):
                            return None
                    if attempt < self.retry_attempts:
                        time.sleep(self.retry_backoff_seconds * attempt)
                        continue
                    break

            # All retry attempts exhausted — wait and retry indefinitely
            status = ""
            if isinstance(last_error, requests.HTTPError) and getattr(last_error, "response", None) is not None:
                status = f" (HTTP {last_error.response.status_code})"
            print(f"[MalClient] All {self.retry_attempts} attempts failed{status}, retrying in {self.rate_limit_wait}s ...")
            time.sleep(self.rate_limit_wait)

    def _iter_paginated(
        self,
        url: str,
        *,
        params: dict | None = None,
        allow_missing: bool = False,
    ) -> Iterable[dict]:
        next_url = url
        next_params = params

        while next_url:
            payload = self._get_json(
                next_url,
                params=next_params,
                allow_missing=allow_missing,
            )
            if payload is None:
                return
            for item in payload.get("data", []):
                yield item
            next_url = payload.get("paging", {}).get("next") or ""
            next_params = None

    def iter_user_anime_list(
        self,
        username: str,
        *,
        fields: Sequence[str] | str | None = None,
        allow_missing: bool = True,
    ) -> Iterable[dict]:
        params = {"nsfw": "true", "limit": 1000}
        formatted_fields = self._format_fields(fields)
        if formatted_fields:
            params["fields"] = formatted_fields
        url = f"{self.base_url}/users/{username}/animelist"
        yield from self._iter_paginated(url, params=params, allow_missing=allow_missing)

    def get_anime_details(
        self,
        anime_id: int,
        *,
        fields: Sequence[str] | str | None = None,
        allow_missing: bool = False,
    ) -> dict | None:
        params = {"nsfw": "true"}
        formatted_fields = self._format_fields(fields)
        if formatted_fields:
            params["fields"] = formatted_fields
        url = f"{self.base_url}/anime/{anime_id}"
        return self._get_json(url, params=params, allow_missing=allow_missing)

    def iter_seasonal_anime(
        self,
        year: int,
        season: str,
        *,
        fields: Sequence[str] | str | None = None,
    ) -> Iterable[dict]:
        params = {"nsfw": "true", "limit": 500, "sort": "anime_num_list_users"}
        formatted_fields = self._format_fields(fields)
        if formatted_fields:
            params["fields"] = formatted_fields
        url = f"{self.base_url}/anime/season/{year}/{season}"
        yield from self._iter_paginated(url, params=params)
