"""CS2 (Counter-Strike 2) Game State Integration adapter.

Parses CS2 GSI JSON payloads into standardized GameEvents. CS2 sends
the full game state on each update — this adapter extracts player state,
round info, and uses diff-based detection for kills/deaths.

Ref: https://developer.valvesoftware.com/wiki/Counter-Strike:_Global_Offensive_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 CS2Adapter(GameAdapter):
    """Adapter for Counter-Strike 2 Game State Integration."""

    ADAPTER_TYPE: ClassVar[str] = "cs2"
    DISPLAY_NAME: ClassVar[str] = "Counter-Strike 2 GSI"
    GAME_NAME: ClassVar[str] = "Counter-Strike 2"
    SUPPORTED_EVENTS: ClassVar[list[str]] = [
        "health",
        "armor",
        "ammo",
        "gold",  # money mapped to gold
        "kill",
        "death",
        "round_start",
        "round_end",
        "objective_captured",  # bomb_planted
        "objective_lost",  # bomb_defused (from attacker perspective)
        "blinded",  # flashbang
        "team_a",  # CT
        "team_b",  # T
    ]

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

        player = payload.get("player", {})
        player_state = player.get("state", {})
        match_stats = player.get("match_stats", {})
        round_info = payload.get("round", {})

        # ── Continuous: health ──
        health = player_state.get("health")
        if health is not None:
            events.append(
                GameEvent(
                    adapter_id=adapter_id,
                    event_type="health",
                    value=max(0.0, min(1.0, float(health) / 100.0)),
                    raw_data={"health": health},
                    timestamp=now,
                )
            )

        # ── Continuous: armor ──
        armor = player_state.get("armor")
        if armor is not None:
            events.append(
                GameEvent(
                    adapter_id=adapter_id,
                    event_type="armor",
                    value=max(0.0, min(1.0, float(armor) / 100.0)),
                    raw_data={"armor": armor},
                    timestamp=now,
                )
            )

        # ── Continuous: money (mapped to gold) ──
        money = player_state.get("money")
        if money is not None:
            events.append(
                GameEvent(
                    adapter_id=adapter_id,
                    event_type="gold",
                    value=max(0.0, min(1.0, float(money) / 16000.0)),
                    raw_data={"money": money},
                    timestamp=now,
                )
            )

        # ── Continuous: ammo ──
        # CS2 reports clip ammo in weapon.ammo_clip and reserve in ammo_reserve
        weapons = player.get("weapons", {})
        active_weapon = _get_active_weapon(weapons)
        if active_weapon:
            clip = active_weapon.get("ammo_clip")
            clip_max = active_weapon.get("ammo_clip_max")
            if clip is not None and clip_max and clip_max > 0:
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="ammo",
                        value=max(0.0, min(1.0, float(clip) / float(clip_max))),
                        raw_data={"ammo_clip": clip, "ammo_clip_max": clip_max},
                        timestamp=now,
                    )
                )

        # ── Trigger: kills (diff-based) ──
        kills = match_stats.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 = match_stats.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: round phase changes ──
        round_phase = round_info.get("phase")
        prev_round_phase = prev_state.get("round_phase")
        new_state["round_phase"] = round_phase

        if round_phase and round_phase != prev_round_phase:
            if round_phase == "live":
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="round_start",
                        value=1.0,
                        raw_data={"round_phase": round_phase},
                        timestamp=now,
                    )
                )
            elif round_phase == "over":
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="round_end",
                        value=1.0,
                        raw_data={"round_phase": round_phase},
                        timestamp=now,
                    )
                )

        # ── Trigger: bomb state ──
        bomb = round_info.get("bomb")
        prev_bomb = prev_state.get("bomb")
        new_state["bomb"] = bomb

        if bomb and bomb != prev_bomb:
            if bomb == "planted":
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="objective_captured",
                        value=1.0,
                        raw_data={"bomb": bomb},
                        timestamp=now,
                    )
                )
            elif bomb == "defused":
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="objective_lost",
                        value=1.0,
                        raw_data={"bomb": bomb},
                        timestamp=now,
                    )
                )

        # ── Trigger: flashbang ──
        flashed = player_state.get("flashed")
        if flashed is not None and flashed > 0:
            prev_flashed = prev_state.get("flashed", 0)
            if prev_flashed == 0:
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="blinded",
                        value=max(0.0, min(1.0, float(flashed) / 255.0)),
                        raw_data={"flashed": flashed},
                        timestamp=now,
                    )
                )
        new_state["flashed"] = flashed if flashed is not None else 0

        # ── Team affiliation ──
        team = player.get("team")
        if team:
            new_state["team"] = team
            if team == "CT":
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="team_a",
                        value=1.0,
                        raw_data={"team": team},
                        timestamp=now,
                    )
                )
            elif team == "T":
                events.append(
                    GameEvent(
                        adapter_id=adapter_id,
                        event_type="team_b",
                        value=1.0,
                        raw_data={"team": team},
                        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 CS2 GSI auth token from payload["auth"]["token"]."""
        expected_token = adapter_config.get("auth_token")
        if not expected_token:
            # No token configured — accept all
            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 CS2 adapter config schema."""
        return {
            "type": "object",
            "properties": {
                "auth_token": {
                    "type": "string",
                    "title": "Auth Token",
                    "description": (
                        "The token string you set in your CS2 GSI config file "
                        "(gamestate_integration_*.cfg). Must match exactly."
                    ),
                },
            },
        }

    @classmethod
    def get_setup_instructions(cls) -> str:
        """Return CS2 GSI setup instructions."""
        return (
            "## CS2 Game State Integration Setup\n\n"
            "1. Navigate to your CS2 config folder:\n"
            "   `Steam/steamapps/common/Counter-Strike Global Offensive/game/csgo/cfg/`\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"
            '       "player_id"           "1"\n'
            '       "player_state"        "1"\n'
            '       "player_weapons"      "1"\n'
            '       "player_match_stats"  "1"\n'
            '       "round"               "1"\n'
            '       "map"                 "1"\n'
            "     }\n"
            "   }\n"
            "   ```\n\n"
            "3. Set the same token in the adapter config above.\n\n"
            "4. Launch CS2 — events will start flowing automatically.\n"
        )

    @classmethod
    def supports_auto_setup(cls) -> bool:
        """CS2 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 CS2 GSI config file automatically.

        Generates an auth token if none is configured. Writes the config
        to the CS2 cfg directory.
        """
        from wled_controller.core.game_integration.steam_finder import find_game_cfg_path

        cfg_path = find_game_cfg_path("cs2")
        if not cfg_path:
            return {
                "success": False,
                "file_path": "",
                "message": "CS2 installation not found. Is Steam/CS2 installed?",
                "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"
            '    "player_id"           "1"\n'
            '    "player_state"        "1"\n'
            '    "player_weapons"      "1"\n'
            '    "player_match_stats"  "1"\n'
            '    "round"               "1"\n'
            '    "map"                 "1"\n'
            "  }\n"
            "}\n"
        )

        file_path = cfg_path / "gamestate_integration_wled.cfg"
        try:
            file_path.write_text(cfg_content, encoding="utf-8")
            logger.info("Wrote CS2 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": "CS2 GSI config written successfully. Restart CS2 to apply.",
            "token_generated": token_generated,
            "adapter_config": config,
        }


def _get_active_weapon(weapons: dict[str, Any]) -> dict[str, Any] | None:
    """Find the currently active weapon from the CS2 weapons dict."""
    for weapon in weapons.values():
        if isinstance(weapon, dict) and weapon.get("state") == "active":
            return weapon
    return None
