from __future__ import annotations

from collections import Counter
from io import BytesIO
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont

from src.cache import TTLCache
from src.config import OG_IMAGE_CACHE_MAX_ENTRIES, OG_IMAGE_CACHE_TTL_SECONDS

_MOIS_FR: dict[int, str] = {
    1: "janvier",
    2: "février",
    3: "mars",
    4: "avril",
    5: "mai",
    6: "juin",
    7: "juillet",
    8: "août",
    9: "septembre",
    10: "octobre",
    11: "novembre",
    12: "décembre",
}

_WEATHER_ICON_MAP: dict[str, str] = {
    "☀️": "sun.png",
    "☀": "sun.png",
    "🌤️": "sun.png",
    "🌤": "sun.png",
    "⛅": "cloud-sun.png",
    "☁️": "cloud.png",
    "☁": "cloud.png",
    "🌫️": "fog.png",
    "🌫": "fog.png",
    "🌦️": "drizzle.png",
    "🌦": "drizzle.png",
    "🌧️": "rain.png",
    "🌧": "rain.png",
    "🌨️": "snow.png",
    "🌨": "snow.png",
    "⛈️": "thunderstorm.png",
    "⛈": "thunderstorm.png",
}


def format_date_fr(iso_date: str) -> str:
    """Convertit une date ISO en date francaise lisible."""
    year, month, day = iso_date.split("-")
    day_value = int(day)
    day_str = "1er" if day_value == 1 else str(day_value)
    return f"{day_str} {_MOIS_FR[int(month)]} {year}"


def format_og_date_range(start: str, end: str) -> str:
    """Retourne une expression de date francaise adaptee au contexte OG."""
    if start == end:
        return f"le {format_date_fr(start)}"

    start_year, start_month, start_day = start.split("-")
    end_year, end_month, _ = end.split("-")
    if start_year == end_year and start_month == end_month:
        start_day_value = int(start_day)
        start_day_str = "1er" if start_day_value == 1 else str(start_day_value)
        return f"du {start_day_str} au {format_date_fr(end)}"

    if start_year == end_year:
        start_day_value = int(start_day)
        start_day_str = "1er" if start_day_value == 1 else str(start_day_value)
        return f"du {start_day_str} {_MOIS_FR[int(start_month)]} au {format_date_fr(end)}"

    return f"du {format_date_fr(start)} au {format_date_fr(end)}"


def aggregate_for_og(daily_summary: list[dict]) -> dict[str, float | str | None]:
    if not daily_summary:
        return {
            "temp_min": None,
            "temp_max": None,
            "dominant_desc": None,
            "dominant_icon": None,
        }

    temp_mins = [day.get("temp_min") for day in daily_summary if day.get("temp_min") is not None]
    temp_maxs = [day.get("temp_max") for day in daily_summary if day.get("temp_max") is not None]

    desc_counts: Counter[str] = Counter()
    for day in daily_summary:
        description = day.get("description")
        if description:
            desc_counts[description] += 1

    dominant_desc = desc_counts.most_common(1)[0][0] if desc_counts else None
    dominant_icon = None
    if dominant_desc:
        for day in daily_summary:
            if day.get("description") == dominant_desc:
                dominant_icon = day.get("icon")
                break

    return {
        "temp_min": min(temp_mins) if temp_mins else None,
        "temp_max": max(temp_maxs) if temp_maxs else None,
        "dominant_desc": dominant_desc,
        "dominant_icon": dominant_icon,
    }


class OGImageService:
    WIDTH = 1200
    HEIGHT = 630
    BG_COLOR = "#fefefe"
    BORDER_COLOR = "#429ab9"
    HEADING_COLOR = "#429ab9"
    DATE_COLOR = "#6b7280"
    DATA_COLOR = "#429ab9"
    ACCENT_COLOR = "#85c65a"
    FOOTER_COLOR = "#6b7280"
    ICON_SIZE = 160

    def __init__(self) -> None:
        src_dir = Path(__file__).resolve().parent
        root_dir = src_dir.parent
        self._font_path = src_dir / "assets" / "fonts" / "Inter-SemiBold.ttf"
        self._logo_path = root_dir / "public" / "assets" / "logo-histometeo.png"
        self._icons_dir = src_dir / "assets" / "weather-icons"
        self._cache: TTLCache[bytes] = TTLCache(
            ttl_seconds=OG_IMAGE_CACHE_TTL_SECONDS,
            max_entries=OG_IMAGE_CACHE_MAX_ENTRIES,
        )

    def _load_font(self, size: int):
        try:
            return ImageFont.truetype(str(self._font_path), size=size)
        except OSError:
            return ImageFont.load_default()

    def _load_weather_icon(self, dominant_icon: str | None) -> Image.Image | None:
        if not dominant_icon:
            return None
        filename = _WEATHER_ICON_MAP.get(dominant_icon)
        if not filename:
            return None

        icon_path = self._icons_dir / filename
        try:
            icon = Image.open(icon_path).convert("RGBA")
            return icon.resize((self.ICON_SIZE, self.ICON_SIZE), Image.LANCZOS)
        except (OSError, ValueError):
            return None

    def generate(
        self,
        commune_name: str,
        start: str,
        end: str,
        temp_min: float | None,
        temp_max: float | None,
        dominant_desc: str | None,
        dominant_icon: str | None,
    ) -> bytes:
        cache_key = f"{commune_name}|{start}|{end}"
        cached = self._cache.get(cache_key)
        if cached is not None:
            return cached

        image = Image.new("RGB", (self.WIDTH, self.HEIGHT), color=self.BG_COLOR)
        draw = ImageDraw.Draw(image)

        font_lg = self._load_font(52)
        font_md = self._load_font(36)
        font_sm = self._load_font(28)

        draw.rectangle([(0, 0), (self.WIDTH, 6)], fill=self.BORDER_COLOR)

        y_cursor = 20

        try:
            logo = Image.open(self._logo_path).convert("RGBA")
            logo_height = 60
            logo_width = int(logo.width * logo_height / logo.height)
            logo = logo.resize((logo_width, logo_height), Image.LANCZOS)
            image.paste(logo, (60, y_cursor), logo)
            y_cursor += logo_height + 30
        except (OSError, ValueError):
            y_cursor += 20

        draw.text((60, y_cursor), commune_name, font=font_lg, fill=self.HEADING_COLOR)
        y_cursor += 70

        date_text = format_og_date_range(start, end)
        draw.text((60, y_cursor), date_text, font=font_md, fill=self.DATE_COLOR)
        y_cursor += 60

        if temp_min is not None and temp_max is not None:
            temp_text = f"{temp_min:.1f} °C → {temp_max:.1f} °C"
            draw.text((60, y_cursor), temp_text, font=font_lg, fill=self.DATA_COLOR)
            y_cursor += 70

        if dominant_desc and dominant_desc != "N/A":
            condition_text = f"Conditions : {dominant_desc.lower()}"
            draw.text((60, y_cursor), condition_text, font=font_sm, fill=self.ACCENT_COLOR)

        weather_icon = self._load_weather_icon(dominant_icon)
        if weather_icon:
            icon_x = self.WIDTH - self.ICON_SIZE - 80
            icon_y = (self.HEIGHT - self.ICON_SIZE) // 2
            image.paste(weather_icon, (icon_x, icon_y), weather_icon)

        draw.text((60, self.HEIGHT - 55), "histometeo.com", font=font_sm, fill=self.FOOTER_COLOR)

        buffer = BytesIO()
        image.save(buffer, format="PNG", optimize=True)
        png_bytes = buffer.getvalue()
        self._cache.set(cache_key, png_bytes)
        return png_bytes
