"""Tests for game integration API routes.

Uses FastAPI TestClient with dependency overrides to test route handlers
in isolation from the real application.
"""

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from wled_controller.api.routes.game_integration import router
from wled_controller.core.game_integration.adapter_registry import AdapterRegistry
from wled_controller.core.game_integration.base_adapter import GameAdapter
from wled_controller.core.game_integration.event_bus import GameEventBus
from wled_controller.core.game_integration.events import GameEvent
from wled_controller.storage.game_integration_store import GameIntegrationStore
from wled_controller.api import dependencies as deps


# ---------------------------------------------------------------------------
# Test adapter for ingestion tests
# ---------------------------------------------------------------------------


class _TestAdapter(GameAdapter):
    ADAPTER_TYPE = "test_adapter"
    DISPLAY_NAME = "Test Adapter"
    GAME_NAME = "Test Game"
    SUPPORTED_EVENTS = ["health", "kill"]

    @classmethod
    def parse_payload(cls, payload, adapter_config, prev_state):
        events = []
        if "health" in payload:
            events.append(
                GameEvent(
                    adapter_id=adapter_config.get("integration_id", "test"),
                    event_type="health",
                    value=payload["health"] / 100.0,
                )
            )
        return events, prev_state

    @classmethod
    def validate_auth(cls, headers, payload, adapter_config):
        token = adapter_config.get("auth_token")
        if not token:
            return True
        return headers.get("x-auth-token") == token

    @classmethod
    def get_config_schema(cls):
        return {
            "type": "object",
            "properties": {
                "auth_token": {"type": "string", "description": "Auth token"},
            },
        }

    @classmethod
    def get_setup_instructions(cls):
        return "Configure the test adapter with an auth token."


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


def _make_app():
    app = FastAPI()
    app.include_router(router)
    return app


@pytest.fixture
def _route_db(tmp_path):
    from wled_controller.storage.database import Database

    db = Database(tmp_path / "test.db")
    yield db
    db.close()


@pytest.fixture
def game_store(_route_db):
    return GameIntegrationStore(_route_db)


@pytest.fixture
def event_bus():
    return GameEventBus()


@pytest.fixture(autouse=True)
def _register_test_adapter():
    """Register and clean up the test adapter for each test."""
    AdapterRegistry.register(_TestAdapter)
    yield
    AdapterRegistry.clear_registry()


@pytest.fixture
def client(game_store, event_bus):
    app = _make_app()

    # Override auth to always pass
    from wled_controller.api.auth import verify_api_key

    app.dependency_overrides[verify_api_key] = lambda: "test-user"
    app.dependency_overrides[deps.get_game_integration_store] = lambda: game_store
    app.dependency_overrides[deps.get_game_event_bus] = lambda: event_bus

    return TestClient(app, raise_server_exceptions=False)


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------


def _create_integration(client, name="Test Integration", adapter_type="test_adapter", **kwargs):
    """Helper to create an integration via API."""
    body = {
        "name": name,
        "adapter_type": adapter_type,
        **kwargs,
    }
    resp = client.post("/api/v1/game-integrations", json=body)
    assert resp.status_code == 201, resp.text
    return resp.json()


# ---------------------------------------------------------------------------
# CRUD tests
# ---------------------------------------------------------------------------


class TestCreateIntegration:
    def test_create_basic(self, client):
        data = _create_integration(client)
        assert data["id"].startswith("gi_")
        assert data["name"] == "Test Integration"
        assert data["adapter_type"] == "test_adapter"
        assert data["enabled"] is True

    def test_create_with_mappings(self, client):
        data = _create_integration(
            client,
            event_mappings=[
                {"event_type": "health", "effect": "gradient", "color": [255, 0, 0]},
                {"event_type": "kill", "effect": "flash", "color": [0, 255, 0]},
            ],
        )
        assert len(data["event_mappings"]) == 2
        assert data["event_mappings"][0]["event_type"] == "health"

    def test_create_with_config(self, client):
        data = _create_integration(
            client,
            adapter_config={"auth_token": "secret123"},
            description="My game",
            tags=["fps"],
        )
        assert data["adapter_config"] == {"auth_token": "secret123"}
        assert data["description"] == "My game"
        assert data["tags"] == ["fps"]

    def test_create_duplicate_name(self, client):
        _create_integration(client, name="Unique")
        resp = client.post(
            "/api/v1/game-integrations",
            json={"name": "Unique", "adapter_type": "test_adapter"},
        )
        assert resp.status_code == 400
        assert "already exists" in resp.json()["detail"]

    def test_create_empty_name(self, client):
        resp = client.post(
            "/api/v1/game-integrations",
            json={"name": "", "adapter_type": "test_adapter"},
        )
        assert resp.status_code == 422  # Pydantic validation


class TestListIntegrations:
    def test_list_empty(self, client):
        resp = client.get("/api/v1/game-integrations")
        assert resp.status_code == 200
        data = resp.json()
        assert data["count"] == 0
        assert data["integrations"] == []

    def test_list_multiple(self, client):
        _create_integration(client, name="Int 1")
        _create_integration(client, name="Int 2")

        resp = client.get("/api/v1/game-integrations")
        data = resp.json()
        assert data["count"] == 2
        names = {i["name"] for i in data["integrations"]}
        assert names == {"Int 1", "Int 2"}


class TestGetIntegration:
    def test_get_existing(self, client):
        created = _create_integration(client)
        resp = client.get(f"/api/v1/game-integrations/{created['id']}")
        assert resp.status_code == 200
        assert resp.json()["name"] == "Test Integration"

    def test_get_nonexistent(self, client):
        resp = client.get("/api/v1/game-integrations/gi_nonexist")
        assert resp.status_code == 404


class TestUpdateIntegration:
    def test_update_name(self, client):
        created = _create_integration(client)
        resp = client.put(
            f"/api/v1/game-integrations/{created['id']}",
            json={"name": "Updated Name"},
        )
        assert resp.status_code == 200
        assert resp.json()["name"] == "Updated Name"

    def test_update_enabled(self, client):
        created = _create_integration(client)
        resp = client.put(
            f"/api/v1/game-integrations/{created['id']}",
            json={"enabled": False},
        )
        assert resp.status_code == 200
        assert resp.json()["enabled"] is False

    def test_update_nonexistent(self, client):
        resp = client.put(
            "/api/v1/game-integrations/gi_nonexist",
            json={"name": "x"},
        )
        assert resp.status_code == 404

    def test_update_to_duplicate_name(self, client):
        _create_integration(client, name="Name A")
        b = _create_integration(client, name="Name B")
        resp = client.put(
            f"/api/v1/game-integrations/{b['id']}",
            json={"name": "Name A"},
        )
        assert resp.status_code == 400


class TestDeleteIntegration:
    def test_delete_existing(self, client):
        created = _create_integration(client)
        resp = client.delete(f"/api/v1/game-integrations/{created['id']}")
        assert resp.status_code == 204

        # Verify it's gone
        resp = client.get(f"/api/v1/game-integrations/{created['id']}")
        assert resp.status_code == 404

    def test_delete_nonexistent(self, client):
        resp = client.delete("/api/v1/game-integrations/gi_nonexist")
        assert resp.status_code == 404


# ---------------------------------------------------------------------------
# Event ingestion tests
# ---------------------------------------------------------------------------


class TestEventIngestion:
    def test_ingest_basic(self, client, event_bus):
        created = _create_integration(client)
        integration_id = created["id"]

        resp = client.post(
            f"/api/v1/game-integrations/{integration_id}/event",
            json={"data": {"health": 75}},
        )
        assert resp.status_code == 204

        # Verify event was published to bus
        recent = event_bus.get_recent_events()
        assert len(recent) == 1
        assert recent[0].event_type == "health"
        assert recent[0].value == 0.75

    def test_ingest_disabled_integration(self, client):
        created = _create_integration(client)
        integration_id = created["id"]

        # Disable the integration
        client.put(
            f"/api/v1/game-integrations/{integration_id}",
            json={"enabled": False},
        )

        resp = client.post(
            f"/api/v1/game-integrations/{integration_id}/event",
            json={"data": {"health": 50}},
        )
        assert resp.status_code == 409

    def test_ingest_nonexistent_integration(self, client):
        resp = client.post(
            "/api/v1/game-integrations/gi_nonexist/event",
            json={"data": {"health": 50}},
        )
        assert resp.status_code == 404

    def test_ingest_auth_failure(self, client):
        created = _create_integration(
            client,
            adapter_config={"auth_token": "correct_token"},
        )
        integration_id = created["id"]

        resp = client.post(
            f"/api/v1/game-integrations/{integration_id}/event",
            json={"data": {"health": 50}},
            headers={"x-auth-token": "wrong_token"},
        )
        assert resp.status_code == 403

    def test_ingest_auth_success(self, client, event_bus):
        created = _create_integration(
            client,
            adapter_config={"auth_token": "correct_token"},
        )
        integration_id = created["id"]

        resp = client.post(
            f"/api/v1/game-integrations/{integration_id}/event",
            json={"data": {"health": 50}},
            headers={"x-auth-token": "correct_token"},
        )
        assert resp.status_code == 204

        recent = event_bus.get_recent_events()
        assert len(recent) == 1


# ---------------------------------------------------------------------------
# Status / diagnostics tests
# ---------------------------------------------------------------------------


class TestStatus:
    def test_status_no_events(self, client):
        created = _create_integration(client)
        resp = client.get(f"/api/v1/game-integrations/{created['id']}/status")
        assert resp.status_code == 200

        data = resp.json()
        assert data["integration_id"] == created["id"]
        assert data["enabled"] is True
        assert data["connected"] is False
        assert data["event_count"] == 0

    def test_status_after_events(self, client):
        created = _create_integration(client)
        integration_id = created["id"]

        # Send an event
        client.post(
            f"/api/v1/game-integrations/{integration_id}/event",
            json={"data": {"health": 80}},
        )

        resp = client.get(f"/api/v1/game-integrations/{integration_id}/status")
        data = resp.json()
        assert data["event_count"] == 1
        assert data["connected"] is True
        assert "health" in data["event_counts_by_type"]

    def test_status_nonexistent(self, client):
        resp = client.get("/api/v1/game-integrations/gi_nonexist/status")
        assert resp.status_code == 404


class TestRecentEvents:
    def test_recent_events_empty(self, client):
        created = _create_integration(client)
        resp = client.get(f"/api/v1/game-integrations/{created['id']}/events")
        assert resp.status_code == 200

        data = resp.json()
        assert data["count"] == 0
        assert data["events"] == []

    def test_recent_events_nonexistent(self, client):
        resp = client.get("/api/v1/game-integrations/gi_nonexist/events")
        assert resp.status_code == 404


# ---------------------------------------------------------------------------
# Adapter metadata tests
# ---------------------------------------------------------------------------


class TestAdapterMetadata:
    def test_list_adapters(self, client):
        resp = client.get("/api/v1/game-adapters")
        assert resp.status_code == 200

        data = resp.json()
        assert data["count"] >= 1

        adapter = data["adapters"][0]
        assert adapter["adapter_type"] == "test_adapter"
        assert adapter["display_name"] == "Test Adapter"
        assert adapter["game_name"] == "Test Game"
        assert "health" in adapter["supported_events"]
        assert "config_schema" in adapter
        assert "setup_instructions" in adapter


# ---------------------------------------------------------------------------
# Preset tests
# ---------------------------------------------------------------------------


class TestPresets:
    def test_list_presets(self, client):
        resp = client.get("/api/v1/game-integrations/presets")
        assert resp.status_code == 200

        data = resp.json()
        assert data["count"] >= 4
        keys = {p["key"] for p in data["presets"]}
        assert "fps_combat" in keys
        assert "moba_health" in keys
        assert "racing" in keys
        assert "generic_alert" in keys

    def test_preset_has_mappings(self, client):
        resp = client.get("/api/v1/game-integrations/presets")
        data = resp.json()
        for preset in data["presets"]:
            assert len(preset["event_mappings"]) > 0
            for m in preset["event_mappings"]:
                assert "event_type" in m
                assert "effect" in m
                assert "color" in m

    def test_apply_preset_replace(self, client):
        created = _create_integration(
            client,
            event_mappings=[{"event_type": "kill", "effect": "flash", "color": [255, 0, 0]}],
        )
        integration_id = created["id"]
        assert len(created["event_mappings"]) == 1

        resp = client.post(
            f"/api/v1/game-integrations/{integration_id}/apply-preset",
            json={"preset_key": "fps_combat", "replace": True},
        )
        assert resp.status_code == 200
        data = resp.json()
        # Should have replaced the single mapping with preset mappings
        assert len(data["event_mappings"]) >= 3

    def test_apply_preset_append(self, client):
        created = _create_integration(
            client,
            event_mappings=[{"event_type": "kill", "effect": "flash", "color": [255, 0, 0]}],
        )
        integration_id = created["id"]

        resp = client.post(
            f"/api/v1/game-integrations/{integration_id}/apply-preset",
            json={"preset_key": "generic_alert", "replace": False},
        )
        assert resp.status_code == 200
        data = resp.json()
        # Should have original + preset mappings
        assert len(data["event_mappings"]) >= 5

    def test_apply_preset_unknown_key(self, client):
        created = _create_integration(client)
        resp = client.post(
            f"/api/v1/game-integrations/{created['id']}/apply-preset",
            json={"preset_key": "nonexistent"},
        )
        assert resp.status_code == 404
        assert "not found" in resp.json()["detail"]

    def test_apply_preset_unknown_integration(self, client):
        resp = client.post(
            "/api/v1/game-integrations/gi_nonexist/apply-preset",
            json={"preset_key": "fps_combat"},
        )
        assert resp.status_code == 404
