"""League of Legends Live Client Data API adapter.

Poll-based adapter that fetches game data from the LoL client's local
HTTP API at https://127.0.0.1:2999/liveclientdata/allgamedata.

Unlike GSI adapters (CS2, Dota 2), this adapter manages its own polling
thread. The thread is started/stopped via start_polling()/stop_polling().

Ref: https://developer.riotgames.com/docs/lol#game-client-api
"""

import threading
import time
from typing import Any, ClassVar

from wled_controller.core.game_integration.base_adapter import GameAdapter
from wled_controller.core.game_integration.events import GameEvent
from wled_controller.utils import get_logger

logger = get_logger(__name__)

# LoL Live Client Data API base URL (local, self-signed SSL)
LOL_API_BASE = "https://127.0.0.1:2999/liveclientdata"


class LoLAdapter(GameAdapter):
    """Adapter for League of Legends Live Client Data API."""

    ADAPTER_TYPE: ClassVar[str] = "lol"
    DISPLAY_NAME: ClassVar[str] = "League of Legends"
    GAME_NAME: ClassVar[str] = "League of Legends"
    SUPPORTED_EVENTS: ClassVar[list[str]] = [
        "health",
        "mana",
        "gold",
        "death",
        "objective_progress",  # respawn mapped to objective_progress
        "speed",  # player level mapped to speed (continuous 0-18)
    ]

    @classmethod
    def parse_payload(
        cls,
        payload: dict[str, Any],
        adapter_config: dict[str, Any],
        prev_state: dict[str, Any],
    ) -> tuple[list[GameEvent], dict[str, Any]]:
        """Parse a LoL Live Client Data payload into standardized events.

        The payload is the full allgamedata response containing
        activePlayer, allPlayers, gameData, and events sections.
        """
        events: list[GameEvent] = []
        new_state = dict(prev_state)
        adapter_id = adapter_config.get("adapter_id", "lol")
        now = time.monotonic()

        active_player = payload.get("activePlayer", {})
        champion_stats = active_player.get("championStats", {})
        summoner_name = active_player.get("summonerName", "")

        # ── Continuous: health ──
        hp = champion_stats.get("currentHealth")
        max_hp = champion_stats.get("maxHealth")
        if hp is not None and max_hp and max_hp > 0:
            events.append(
                GameEvent(
                    adapter_id=adapter_id,
                    event_type="health",
                    value=max(0.0, min(1.0, float(hp) / float(max_hp))),
                    raw_data={"health": hp, "max_health": max_hp},
                    timestamp=now,
                )
            )

        # ── Continuous: mana (resourceValue) ──
        resource_val = champion_stats.get("resourceValue")
        resource_max = champion_stats.get("resourceMax")
        if resource_val is not None and resource_max and resource_max > 0:
            events.append(
                GameEvent(
                    adapter_id=adapter_id,
                    event_type="mana",
                    value=max(0.0, min(1.0, float(resource_val) / float(resource_max))),
                    raw_data={"mana": resource_val, "max_mana": resource_max},
                    timestamp=now,
                )
            )

        # ── Continuous: level (mapped to speed, normalized 0-18) ──
        level = active_player.get("level")
        if level is not None:
            events.append(
                GameEvent(
                    adapter_id=adapter_id,
                    event_type="speed",
                    value=max(0.0, min(1.0, float(level) / 18.0)),
                    raw_data={"level": level},
                    timestamp=now,
                )
            )

        # ── Continuous: gold ──
        # Gold is available per player in allPlayers array
        all_players = payload.get("allPlayers", [])
        player_data = _find_player_by_name(all_players, summoner_name)
        if player_data:
            gold = player_data.get("currentGold")
            if gold is not None:
                max_gold = float(adapter_config.get("max_gold", 30000))
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="gold",
                        value=max(0.0, min(1.0, float(gold) / max_gold)),
                        raw_data={"gold": gold},
                        timestamp=now,
                    )
                )

        # ── Trigger: death detection ──
        # If health drops to 0, player is dead
        if hp is not None and float(hp) <= 0:
            was_alive = prev_state.get("alive", True)
            if was_alive:
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="death",
                        value=1.0,
                        raw_data={"health": hp},
                        timestamp=now,
                    )
                )
            new_state["alive"] = False
        elif hp is not None and float(hp) > 0:
            was_alive = prev_state.get("alive", True)
            if not was_alive:
                # Respawn detected
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="objective_progress",
                        value=1.0,
                        raw_data={"respawn": True, "health": hp},
                        timestamp=now,
                    )
                )
            new_state["alive"] = True

        return events, new_state

    @classmethod
    def validate_auth(
        cls,
        headers: dict[str, str],
        payload: dict[str, Any],
        adapter_config: dict[str, Any],
    ) -> bool:
        """LoL Live Client API is local-only — no auth needed."""
        return True

    @classmethod
    def get_config_schema(cls) -> dict[str, Any]:
        """Return LoL adapter config schema."""
        return {
            "type": "object",
            "properties": {
                "poll_interval_ms": {
                    "type": "integer",
                    "title": "Poll Interval (ms)",
                    "description": "How often to poll the LoL client API (default: 500ms).",
                    "default": 500,
                    "minimum": 100,
                    "maximum": 5000,
                },
                "max_gold": {
                    "type": "number",
                    "title": "Max Gold",
                    "description": "Maximum gold for normalization (default: 30000).",
                    "default": 30000,
                },
            },
        }

    @classmethod
    def get_setup_instructions(cls) -> str:
        """Return LoL setup instructions."""
        return (
            "## League of Legends Live Client Data Setup\n\n"
            "The LoL Live Client Data API runs automatically when you're in a game.\n\n"
            "**Requirements:**\n"
            "- WLED Screen Controller must run on the same machine as LoL\n"
            "- The LoL client exposes data at `https://127.0.0.1:2999`\n"
            "- No configuration needed in the LoL client\n\n"
            "**How it works:**\n"
            "- The adapter polls the local API at the configured interval\n"
            "- Data is only available while you're in an active game\n"
            "- The API uses a self-signed SSL certificate (handled automatically)\n\n"
            "**Note:** This adapter uses polling mode. Enable the integration, "
            "then start a game — events will appear automatically.\n"
        )


class LoLPoller:
    """Polling thread manager for the LoL Live Client Data API.

    Creates a daemon thread that periodically fetches game data from the
    local LoL client API and passes it through the adapter.
    """

    def __init__(
        self,
        adapter_config: dict[str, Any],
        callback: Any,  # Callable[[dict], None]
    ) -> None:
        self._adapter_config = adapter_config
        self._callback = callback
        self._stop_event = threading.Event()
        self._thread: threading.Thread | None = None
        self._poll_interval = adapter_config.get("poll_interval_ms", 500) / 1000.0

    def start(self) -> None:
        """Start the polling thread."""
        if self._thread and self._thread.is_alive():
            logger.warning("LoL poller already running")
            return

        self._stop_event.clear()
        self._thread = threading.Thread(
            target=self._poll_loop,
            name="lol-poller",
            daemon=True,
        )
        self._thread.start()
        logger.info("LoL poller started (interval: %.1fs)", self._poll_interval)

    def stop(self) -> None:
        """Stop the polling thread."""
        self._stop_event.set()
        if self._thread:
            self._thread.join(timeout=5.0)
            self._thread = None
        logger.info("LoL poller stopped")

    @property
    def is_running(self) -> bool:
        """Check if the polling thread is alive."""
        return self._thread is not None and self._thread.is_alive()

    def _poll_loop(self) -> None:
        """Main polling loop — runs in a separate daemon thread."""
        import urllib.request
        import ssl
        import json

        # LoL uses self-signed cert — skip verification
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        url = f"{LOL_API_BASE}/allgamedata"

        while not self._stop_event.is_set():
            try:
                req = urllib.request.Request(url)
                with urllib.request.urlopen(req, timeout=2, context=ctx) as resp:
                    data = json.loads(resp.read().decode("utf-8"))
                    self._callback(data)
            except Exception:
                # Game not running or API not available — silently retry
                pass

            self._stop_event.wait(self._poll_interval)


def _find_player_by_name(
    all_players: list[dict[str, Any]],
    summoner_name: str,
) -> dict[str, Any] | None:
    """Find the active player's data in the allPlayers array."""
    for player in all_players:
        if player.get("summonerName") == summoner_name:
            return player
    return None
