import json
import logging
import os
import tempfile
import time
from typing import Any, Dict, List, Optional

import requests
from django.conf import settings
from wagtail.models import Page

from ocyan.plugin.wagtail.localization import (
    localize_internal_path,
    resolve_page_for_language,
)
from ocyan.plugin.wagtail.middleware import get_current_request

logger = logging.getLogger(__name__)
DEFAULT_GOOGLE_FONTS_CACHE_PATH = "/tmp/google-fonts.json"
DEFAULT_GOOGLE_FONTS_CACHE_TTL_SECONDS = 60 * 60 * 24
DEFAULT_GOOGLE_FONTS_RETRY_COOLDOWN_SECONDS = 60 * 10
_google_fonts_state = {
    "next_retry_at": 0,
    "last_error_log_at": 0,
    "missing_key_logged": False,
}


def exclude_none_values(d: dict) -> dict:
    """
    Exclude items where value is None from dict

    >>> d = {'a': None, 'b': 'myname', 'c': 122}
    >>> exclude_none_values(d)
    {'b': 'myname', 'c': 122}
    """
    return {k: v for k, v in d.items() if v is not None}


def get_google_fonts():
    """
    Try to get the fonts from /tmp/google-fonts.json.
    If it doesn't exists get from google and save in /tmp/.
    """
    cache_path = getattr(
        settings, "COYOTE_GOOGLE_FONTS_CACHE_PATH", DEFAULT_GOOGLE_FONTS_CACHE_PATH
    )
    cache_ttl_seconds = _get_cache_ttl_seconds()
    retry_cooldown_seconds = _get_retry_cooldown_seconds()

    cached_fonts = _read_cached_fonts(cache_path, cache_ttl_seconds)
    if cached_fonts is not None:
        return cached_fonts
    now = int(time.time())
    if now < _google_fonts_state["next_retry_at"]:
        return _read_cached_fonts(cache_path, cache_ttl_seconds, allow_stale=True) or []

    api_key = getattr(settings, "GOOGLE_FONTS_API_KEY", None)
    if not api_key:
        if not _google_fonts_state["missing_key_logged"]:
            logger.warning("GOOGLE_FONTS_API_KEY is not configured.")
            _google_fonts_state["missing_key_logged"] = True
        return []

    google_fonts_url = f"https://www.googleapis.com/webfonts/v1/webfonts?key={api_key}"

    try:
        response = requests.get(google_fonts_url, timeout=(5, 30))
        response.raise_for_status()
        data = response.json()
        fonts = data.get("items", [])
        if not isinstance(fonts, list):
            logger.warning("Google Fonts API response missing 'items' list.")
            return (
                _read_cached_fonts(cache_path, cache_ttl_seconds, allow_stale=True)
                or []
            )

        _write_cached_fonts(cache_path, fonts)

        return fonts
    except (requests.RequestException, ValueError, OSError) as exc:
        now = int(time.time())
        if now >= _google_fonts_state["last_error_log_at"] + retry_cooldown_seconds:
            logger.warning("Unable to load Google Fonts: %s", exc)
            _google_fonts_state["last_error_log_at"] = now
        _google_fonts_state["next_retry_at"] = now + retry_cooldown_seconds
        return _read_cached_fonts(cache_path, cache_ttl_seconds, allow_stale=True) or []


def _get_cache_ttl_seconds() -> int:
    ttl = getattr(
        settings,
        "COYOTE_GOOGLE_FONTS_CACHE_TTL_SECONDS",
        DEFAULT_GOOGLE_FONTS_CACHE_TTL_SECONDS,
    )
    try:
        ttl_int = int(ttl)
    except (TypeError, ValueError):
        logger.warning(
            "Invalid COYOTE_GOOGLE_FONTS_CACHE_TTL_SECONDS=%r, using default %s.",
            ttl,
            DEFAULT_GOOGLE_FONTS_CACHE_TTL_SECONDS,
        )
        return DEFAULT_GOOGLE_FONTS_CACHE_TTL_SECONDS
    return max(ttl_int, 0)


def _get_retry_cooldown_seconds() -> int:
    retry_cooldown = getattr(
        settings,
        "COYOTE_GOOGLE_FONTS_RETRY_COOLDOWN_SECONDS",
        DEFAULT_GOOGLE_FONTS_RETRY_COOLDOWN_SECONDS,
    )
    try:
        retry_cooldown_int = int(retry_cooldown)
    except (TypeError, ValueError):
        logger.warning(
            "Invalid COYOTE_GOOGLE_FONTS_RETRY_COOLDOWN_SECONDS=%r, using default %s.",
            retry_cooldown,
            DEFAULT_GOOGLE_FONTS_RETRY_COOLDOWN_SECONDS,
        )
        return DEFAULT_GOOGLE_FONTS_RETRY_COOLDOWN_SECONDS
    return max(retry_cooldown_int, 0)


def _read_cached_fonts(
    cache_path: str, cache_ttl_seconds: int, allow_stale: bool = False
) -> Optional[List[Dict[str, Any]]]:
    try:
        with open(cache_path, "r", encoding="utf-8") as f:
            cached_fonts = json.load(f)
        if not isinstance(cached_fonts, list):
            return None
        if allow_stale:
            return cached_fonts

        cache_age = max(0, int(time.time() - os.path.getmtime(cache_path)))
        if cache_ttl_seconds == 0:
            return None
        if cache_age <= cache_ttl_seconds:
            return cached_fonts
        return None
    except FileNotFoundError:
        return None
    except (json.JSONDecodeError, OSError):
        logger.info("Resetting invalid Google Fonts cache %s.", cache_path)
        try:
            if os.path.exists(cache_path):
                os.remove(cache_path)
        except OSError:
            pass
        return None


def _write_cached_fonts(cache_path: str, fonts: List[Dict[str, Any]]) -> None:
    tmp_path = None
    cache_dir = os.path.dirname(cache_path) or "."
    os.makedirs(cache_dir, exist_ok=True)
    try:
        fd, tmp_path = tempfile.mkstemp(
            prefix=".google-fonts-", suffix=".json", dir=cache_dir
        )
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(fonts, f)
        os.replace(tmp_path, cache_path)
    finally:
        if tmp_path and os.path.exists(tmp_path):
            os.remove(tmp_path)


def get_compressor_cache_key(key):
    """
    compressor cache per domain
    """

    if request := get_current_request():
        return key + request.get_host()

    return key


class LinkPageString(str):
    def get_url(self):
        return localize_internal_path(self, request=get_current_request())


class LocalizedLinkPage:
    def __init__(self, page: Page):
        self.page = page

    @property
    def resolved_page(self):
        return resolve_page_for_language(self.page, request=get_current_request())

    def get_url(self):
        return self.resolved_page.get_url(request=get_current_request())

    @property
    def pk(self):
        return self.page.pk

    def __getattr__(self, item):
        return getattr(self.resolved_page, item)
