"""Dota 2 Game State Integration adapter.

Parses Dota 2 GSI JSON payloads into standardized GameEvents. Dota 2's GSI
format is similar to CS2 but with different payload structure focused on
hero state, match info, and gold.

Ref: https://developer.valvesoftware.com/wiki/Dota_2_Game_State_Integration
"""

import secrets
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__)


class Dota2Adapter(GameAdapter):
    """Adapter for Dota 2 Game State Integration."""

    ADAPTER_TYPE: ClassVar[str] = "dota2"
    DISPLAY_NAME: ClassVar[str] = "Dota 2 GSI"
    GAME_NAME: ClassVar[str] = "Dota 2"
    SUPPORTED_EVENTS: ClassVar[list[str]] = [
        "health",
        "mana",
        "kill",
        "death",
        "match_start",
        "match_end",
        "gold",
    ]

    @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 Dota 2 GSI payload into standardized events."""
        events: list[GameEvent] = []
        new_state = dict(prev_state)
        adapter_id = adapter_config.get("adapter_id", "dota2")
        now = time.monotonic()

        hero = payload.get("hero", {})
        player = payload.get("player", {})
        game_map = payload.get("map", {})

        # ── Continuous: health ──
        hp = hero.get("health")
        max_hp = hero.get("max_health")
        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 ──
        mp = hero.get("mana")
        max_mp = hero.get("max_mana")
        if mp is not None and max_mp and max_mp > 0:
            events.append(
                GameEvent(
                    adapter_id=adapter_id,
                    event_type="mana",
                    value=max(0.0, min(1.0, float(mp) / float(max_mp))),
                    raw_data={"mana": mp, "max_mana": max_mp},
                    timestamp=now,
                )
            )

        # ── Continuous: gold ──
        gold = player.get("gold")
        if gold is not None:
            # Normalize to 0-1 with a reasonable max (99999 net worth)
            max_gold = float(adapter_config.get("max_gold", 99999))
            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: kills (diff-based) ──
        kills = player.get("kills")
        if kills is not None:
            prev_kills = prev_state.get("kills")
            new_state["kills"] = kills
            if prev_kills is not None and kills > prev_kills:
                for _ in range(kills - prev_kills):
                    events.append(
                        GameEvent(
                            adapter_id=adapter_id,
                            event_type="kill",
                            value=1.0,
                            raw_data={"kills": kills, "prev_kills": prev_kills},
                            timestamp=now,
                        )
                    )

        # ── Trigger: deaths (diff-based) ──
        deaths = player.get("deaths")
        if deaths is not None:
            prev_deaths = prev_state.get("deaths")
            new_state["deaths"] = deaths
            if prev_deaths is not None and deaths > prev_deaths:
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="death",
                        value=1.0,
                        raw_data={"deaths": deaths, "prev_deaths": prev_deaths},
                        timestamp=now,
                    )
                )

        # ── Trigger: match phase ──
        game_state = game_map.get("game_state")
        prev_game_state = prev_state.get("game_state")
        new_state["game_state"] = game_state

        if game_state and game_state != prev_game_state:
            # Dota 2 states: DOTA_GAMERULES_STATE_*
            if game_state in (
                "DOTA_GAMERULES_STATE_PRE_GAME",
                "DOTA_GAMERULES_STATE_GAME_IN_PROGRESS",
            ):
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="match_start",
                        value=1.0,
                        raw_data={"game_state": game_state},
                        timestamp=now,
                    )
                )
            elif game_state in (
                "DOTA_GAMERULES_STATE_POST_GAME",
                "DOTA_GAMERULES_STATE_DISCONNECT",
            ):
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="match_end",
                        value=1.0,
                        raw_data={"game_state": game_state},
                        timestamp=now,
                    )
                )

        return events, new_state

    @classmethod
    def validate_auth(
        cls,
        headers: dict[str, str],
        payload: dict[str, Any],
        adapter_config: dict[str, Any],
    ) -> bool:
        """Validate Dota 2 GSI auth token from payload["auth"]["token"]."""
        expected_token = adapter_config.get("auth_token")
        if not expected_token:
            return True

        auth_section = payload.get("auth", {})
        actual_token = auth_section.get("token", "")
        return bool(actual_token and actual_token == expected_token)

    @classmethod
    def get_config_schema(cls) -> dict[str, Any]:
        """Return Dota 2 adapter config schema."""
        return {
            "type": "object",
            "properties": {
                "auth_token": {
                    "type": "string",
                    "title": "Auth Token",
                    "description": (
                        "The token string from your Dota 2 GSI config file. "
                        "Must match the token in gamestate_integration_*.cfg."
                    ),
                },
                "max_gold": {
                    "type": "number",
                    "title": "Max Gold",
                    "description": "Maximum gold value for normalization (default: 99999).",
                    "default": 99999,
                },
            },
        }

    @classmethod
    def get_setup_instructions(cls) -> str:
        """Return Dota 2 GSI setup instructions."""
        return (
            "## Dota 2 Game State Integration Setup\n\n"
            "1. Navigate to your Dota 2 config folder:\n"
            "   `Steam/steamapps/common/dota 2 beta/game/dota/cfg/gamestate_integration/`\n\n"
            "2. Create a file named `gamestate_integration_wled.cfg` with:\n"
            "   ```\n"
            '   "WLED Screen Controller"\n'
            "   {\n"
            '     "uri"          "http://<YOUR_IP>:8080/api/v1/game-integrations/<ID>/event"\n'
            '     "timeout"      "5.0"\n'
            '     "buffer"       "0.1"\n'
            '     "throttle"     "0.1"\n'
            '     "heartbeat"    "30.0"\n'
            '     "auth"\n'
            "     {\n"
            '       "token"      "<YOUR_TOKEN>"\n'
            "     }\n"
            '     "data"\n'
            "     {\n"
            '       "hero"       "1"\n'
            '       "player"     "1"\n'
            '       "map"        "1"\n'
            "     }\n"
            "   }\n"
            "   ```\n\n"
            "3. Set the same token in the adapter config above.\n\n"
            "4. Launch Dota 2 and enter a match — events will start flowing.\n"
        )

    @classmethod
    def supports_auto_setup(cls) -> bool:
        """Dota 2 supports automatic GSI config file generation."""
        return True

    @classmethod
    def auto_setup(
        cls,
        integration_id: str,
        adapter_config: dict[str, Any],
        server_url: str,
    ) -> dict[str, Any]:
        """Write a Dota 2 GSI config file automatically.

        Generates an auth token if none is configured. Writes the config
        to the Dota 2 gamestate_integration subdirectory (created if needed).
        """
        from wled_controller.core.game_integration.steam_finder import find_game_cfg_path

        cfg_path = find_game_cfg_path("dota2")
        if not cfg_path:
            return {
                "success": False,
                "file_path": "",
                "message": "Dota 2 installation not found. Is Steam/Dota 2 installed?",
                "token_generated": False,
                "adapter_config": adapter_config,
            }

        # Dota 2 GSI configs live in a gamestate_integration/ subdirectory
        gsi_dir = cfg_path / "gamestate_integration"
        try:
            gsi_dir.mkdir(parents=False, exist_ok=True)
        except OSError as e:
            return {
                "success": False,
                "file_path": str(gsi_dir),
                "message": f"Failed to create gamestate_integration directory: {e}",
                "token_generated": False,
                "adapter_config": adapter_config,
            }

        # Generate auth token if not set
        token_generated = False
        config = dict(adapter_config)
        auth_token = config.get("auth_token", "")
        if not auth_token:
            auth_token = secrets.token_hex(16)
            config["auth_token"] = auth_token
            token_generated = True

        uri = f"{server_url}/api/v1/game-integrations/{integration_id}/event"

        cfg_content = (
            '"WLED Screen Controller"\n'
            "{\n"
            f'  "uri"          "{uri}"\n'
            '  "timeout"      "5.0"\n'
            '  "buffer"       "0.1"\n'
            '  "throttle"     "0.1"\n'
            '  "heartbeat"    "30.0"\n'
            '  "auth"\n'
            "  {\n"
            f'    "token"      "{auth_token}"\n'
            "  }\n"
            '  "data"\n'
            "  {\n"
            '    "hero"        "1"\n'
            '    "player"      "1"\n'
            '    "map"         "1"\n'
            "  }\n"
            "}\n"
        )

        file_path = gsi_dir / "gamestate_integration_wled.cfg"
        try:
            file_path.write_text(cfg_content, encoding="utf-8")
            logger.info("Wrote Dota 2 GSI config to %s", file_path)
        except OSError as e:
            return {
                "success": False,
                "file_path": str(file_path),
                "message": f"Failed to write config file: {e}",
                "token_generated": False,
                "adapter_config": adapter_config,
            }

        return {
            "success": True,
            "file_path": str(file_path),
            "message": "Dota 2 GSI config written successfully. Restart Dota 2 to apply.",
            "token_generated": token_generated,
            "adapter_config": config,
        }
