"""Game integration API routes.

CRUD for game integration configs, event ingestion endpoint,
adapter metadata, and diagnostics.
"""

import threading
import time
from typing import Any

from fastapi import APIRouter, Depends, HTTPException, Request

from wled_controller.api.auth import AuthRequired
from wled_controller.api.dependencies import (
    fire_entity_event,
    get_database,
    get_game_integration_store,
    get_game_event_bus,
)
from wled_controller.api.schemas.game_integration import (
    AdapterInfoResponse,
    AdapterListResponse,
    ApplyPresetRequest,
    AutoSetupResponse,
    EffectPresetResponse,
    EventMappingSchema,
    GameEventPayload,
    GameEventResponse,
    GameIntegrationCreate,
    GameIntegrationListResponse,
    GameIntegrationResponse,
    GameIntegrationStatusResponse,
    GameIntegrationUpdate,
    PresetListResponse,
    RecentEventsResponse,
)
from wled_controller.core.game_integration.adapter_registry import AdapterRegistry
from wled_controller.core.game_integration.event_bus import GameEventBus
from wled_controller.core.game_integration.events import GameEvent
from wled_controller.storage.base_store import EntityNotFoundError
from wled_controller.storage.game_integration import EventMapping
from wled_controller.storage.game_integration_store import GameIntegrationStore
from wled_controller.utils import get_logger

logger = get_logger(__name__)

router = APIRouter()

# ── Per-integration runtime state (in-memory, not persisted) ──────────────

_integration_state_lock = threading.Lock()

# integration_id -> prev_state dict for diff-based trigger detection
_prev_states: dict[str, dict[str, Any]] = {}

# integration_id -> runtime stats
_integration_stats: dict[str, dict[str, Any]] = {}


def _schema_to_fields(schema: dict[str, Any]) -> list[dict[str, Any]]:
    """Convert a JSON Schema object into a flat list of field descriptors.

    The frontend expects [{name, type, label, default, required, hint}, ...].
    """
    properties = schema.get("properties", {})
    required_set = set(schema.get("required", []))
    fields: list[dict[str, Any]] = []
    for name, prop in properties.items():
        field: dict[str, Any] = {
            "name": name,
            "type": prop.get("type", "string"),
            "label": prop.get("title", name),
        }
        if "default" in prop:
            field["default"] = prop["default"]
        if name in required_set:
            field["required"] = True
        desc = prop.get("description")
        if desc:
            field["hint"] = desc
        fields.append(field)
    return fields


def _get_prev_state(integration_id: str) -> dict[str, Any]:
    """Get or create the prev_state dict for an integration."""
    with _integration_state_lock:
        if integration_id not in _prev_states:
            _prev_states[integration_id] = {}
        return _prev_states[integration_id]


def _set_prev_state(integration_id: str, state: dict[str, Any]) -> None:
    """Update the prev_state dict for an integration."""
    with _integration_state_lock:
        _prev_states[integration_id] = state


def _record_events(integration_id: str, events: list[GameEvent]) -> None:
    """Record event stats for an integration."""
    with _integration_state_lock:
        if integration_id not in _integration_stats:
            _integration_stats[integration_id] = {
                "event_count": 0,
                "event_counts_by_type": {},
                "last_event_time": None,
            }
        stats = _integration_stats[integration_id]
        for event in events:
            stats["event_count"] += 1
            stats["event_counts_by_type"][event.event_type] = (
                stats["event_counts_by_type"].get(event.event_type, 0) + 1
            )
            stats["last_event_time"] = event.timestamp


def _get_stats(integration_id: str) -> dict[str, Any]:
    """Get runtime stats for an integration."""
    with _integration_state_lock:
        return _integration_stats.get(
            integration_id,
            {"event_count": 0, "event_counts_by_type": {}, "last_event_time": None},
        )


def _cleanup_state(integration_id: str) -> None:
    """Remove runtime state for a deleted integration."""
    with _integration_state_lock:
        _prev_states.pop(integration_id, None)
        _integration_stats.pop(integration_id, None)


# ── Helper: convert config to response ────────────────────────────────────


def _config_to_response(config: Any) -> GameIntegrationResponse:
    """Convert a GameIntegrationConfig to its API response."""
    from wled_controller.api.schemas.game_integration import EventMappingSchema

    return GameIntegrationResponse(
        id=config.id,
        name=config.name,
        adapter_type=config.adapter_type,
        enabled=config.enabled,
        adapter_config=config.adapter_config,
        event_mappings=[
            EventMappingSchema(
                event_type=m.event_type,
                effect=m.effect,
                color=m.color,
                duration_ms=m.duration_ms,
                intensity=m.intensity,
                priority=m.priority,
            )
            for m in config.event_mappings
        ],
        created_at=config.created_at,
        updated_at=config.updated_at,
        description=config.description,
        tags=config.tags,
    )


# ── Effect Presets (must be before /{integration_id} routes) ────────────


@router.get(
    "/api/v1/game-integrations/presets",
    response_model=PresetListResponse,
    tags=["Game Integration"],
)
async def list_presets(_auth: AuthRequired):
    """List all available built-in effect presets."""
    from wled_controller.core.game_integration.presets import get_all_presets

    presets = get_all_presets()
    responses = [
        EffectPresetResponse(
            key=p.key,
            name=p.name,
            description=p.description,
            target_game_types=list(p.target_game_types),
            event_mappings=[
                EventMappingSchema(
                    event_type=m.event_type,
                    effect=m.effect,
                    color=list(m.color),
                    duration_ms=m.duration_ms,
                    intensity=m.intensity,
                    priority=m.priority,
                )
                for m in p.event_mappings
            ],
        )
        for p in presets
    ]
    return PresetListResponse(presets=responses, count=len(responses))


# ── CRUD Endpoints ────────────────────────────────────────────────────────


@router.get(
    "/api/v1/game-integrations",
    response_model=GameIntegrationListResponse,
    tags=["Game Integration"],
)
async def list_integrations(
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
):
    """List all game integration configs."""
    try:
        configs = store.get_all_integrations()
        responses = [_config_to_response(c) for c in configs]
        return GameIntegrationListResponse(
            integrations=responses,
            count=len(responses),
        )
    except Exception as e:
        logger.error("Failed to list game integrations: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.post(
    "/api/v1/game-integrations",
    response_model=GameIntegrationResponse,
    tags=["Game Integration"],
    status_code=201,
)
async def create_integration(
    data: GameIntegrationCreate,
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
):
    """Create a new game integration config."""
    try:
        mappings = [
            EventMapping(
                event_type=m.event_type,
                effect=m.effect,
                color=list(m.color),
                duration_ms=m.duration_ms,
                intensity=m.intensity,
                priority=m.priority,
            )
            for m in data.event_mappings
        ]

        config = store.create_integration(
            name=data.name,
            adapter_type=data.adapter_type,
            enabled=data.enabled,
            adapter_config=data.adapter_config,
            event_mappings=mappings,
            description=data.description,
            tags=data.tags,
        )

        fire_entity_event("game_integration", "created", config.id)
        return _config_to_response(config)

    except EntityNotFoundError as e:
        raise HTTPException(status_code=404, detail=str(e))
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error("Failed to create game integration: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.get(
    "/api/v1/game-integrations/{integration_id}",
    response_model=GameIntegrationResponse,
    tags=["Game Integration"],
)
async def get_integration(
    integration_id: str,
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
):
    """Get a game integration config by ID."""
    try:
        config = store.get_integration(integration_id)
        return _config_to_response(config)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"Game integration {integration_id} not found")


@router.put(
    "/api/v1/game-integrations/{integration_id}",
    response_model=GameIntegrationResponse,
    tags=["Game Integration"],
)
async def update_integration(
    integration_id: str,
    data: GameIntegrationUpdate,
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
):
    """Update a game integration config."""
    try:
        mappings = None
        if data.event_mappings is not None:
            mappings = [
                EventMapping(
                    event_type=m.event_type,
                    effect=m.effect,
                    color=list(m.color),
                    duration_ms=m.duration_ms,
                    intensity=m.intensity,
                    priority=m.priority,
                )
                for m in data.event_mappings
            ]

        config = store.update_integration(
            integration_id=integration_id,
            name=data.name,
            adapter_type=data.adapter_type,
            enabled=data.enabled,
            adapter_config=data.adapter_config,
            event_mappings=mappings,
            description=data.description,
            tags=data.tags,
        )

        fire_entity_event("game_integration", "updated", integration_id)
        return _config_to_response(config)

    except EntityNotFoundError as e:
        raise HTTPException(status_code=404, detail=str(e))
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error("Failed to update game integration: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


@router.delete(
    "/api/v1/game-integrations/{integration_id}",
    status_code=204,
    tags=["Game Integration"],
)
async def delete_integration(
    integration_id: str,
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
):
    """Delete a game integration config."""
    try:
        store.delete_integration(integration_id)
        _cleanup_state(integration_id)
        fire_entity_event("game_integration", "deleted", integration_id)
    except EntityNotFoundError as e:
        raise HTTPException(status_code=404, detail=str(e))
    except Exception as e:
        logger.error("Failed to delete game integration: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


# ── Event Ingestion ───────────────────────────────────────────────────────


@router.post(
    "/api/v1/game-integrations/{integration_id}/event",
    tags=["Game Integration"],
    status_code=204,
)
async def ingest_event(
    integration_id: str,
    payload: GameEventPayload,
    request: Request,
    store: GameIntegrationStore = Depends(get_game_integration_store),
    event_bus: GameEventBus = Depends(get_game_event_bus),
):
    """Receive a game event payload from a game client.

    This endpoint is designed for low-latency ingestion (games send at
    16-64 Hz). Auth is adapter-level: the adapter's validate_auth() is
    called before standard API auth.

    No AuthRequired dependency — adapter-level auth is used instead.
    """
    try:
        config = store.get_integration(integration_id)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"Game integration {integration_id} not found")

    if not config.enabled:
        raise HTTPException(status_code=409, detail="Integration is disabled")

    # Look up adapter
    try:
        adapter_cls = AdapterRegistry.get_adapter(config.adapter_type)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    # Adapter-level auth check
    headers = dict(request.headers)
    if not adapter_cls.validate_auth(headers, payload.data, config.adapter_config):
        raise HTTPException(status_code=403, detail="Adapter authentication failed")

    # Parse payload through adapter
    prev_state = _get_prev_state(integration_id)
    try:
        events, new_state = adapter_cls.parse_payload(
            payload.data, config.adapter_config, prev_state
        )
    except Exception as e:
        logger.error(
            "Adapter %s failed to parse payload for %s: %s",
            config.adapter_type,
            integration_id,
            e,
        )
        raise HTTPException(status_code=400, detail=f"Failed to parse payload: {e}")

    _set_prev_state(integration_id, new_state)

    # Publish events to the bus
    for event in events:
        event_bus.publish(event)

    # Track stats
    if events:
        _record_events(integration_id, events)


# ── Status / Diagnostics ─────────────────────────────────────────────────


@router.get(
    "/api/v1/game-integrations/{integration_id}/status",
    response_model=GameIntegrationStatusResponse,
    tags=["Game Integration"],
)
async def get_integration_status(
    integration_id: str,
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
):
    """Get runtime status for a game integration."""
    try:
        config = store.get_integration(integration_id)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"Game integration {integration_id} not found")

    stats = _get_stats(integration_id)

    # Consider "connected" if we received an event in the last 30 seconds
    last_event_time = stats["last_event_time"]
    connected = False
    if last_event_time is not None:
        connected = (time.monotonic() - last_event_time) < 30.0

    return GameIntegrationStatusResponse(
        integration_id=integration_id,
        enabled=config.enabled,
        connected=connected,
        last_event_time=last_event_time,
        event_count=stats["event_count"],
        event_counts_by_type=stats["event_counts_by_type"],
    )


@router.get(
    "/api/v1/game-integrations/{integration_id}/events",
    response_model=RecentEventsResponse,
    tags=["Game Integration"],
)
async def get_recent_events(
    integration_id: str,
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
    event_bus: GameEventBus = Depends(get_game_event_bus),
    limit: int = 50,
):
    """Get recent events for a game integration (for debugging)."""
    try:
        store.get_integration(integration_id)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"Game integration {integration_id} not found")

    # Filter bus recent events to this integration
    all_recent = event_bus.get_recent_events(limit=200)
    filtered = [e for e in all_recent if e.adapter_id == integration_id][-limit:]

    event_responses = [
        GameEventResponse(
            adapter_id=e.adapter_id,
            event_type=e.event_type,
            value=e.value,
            timestamp=e.timestamp,
            raw_data=e.raw_data,
        )
        for e in filtered
    ]

    return RecentEventsResponse(
        integration_id=integration_id,
        events=event_responses,
        count=len(event_responses),
    )


# ── Adapter Metadata ─────────────────────────────────────────────────────


@router.get(
    "/api/v1/game-adapters",
    response_model=AdapterListResponse,
    tags=["Game Integration"],
)
async def list_adapters(_auth: AuthRequired):
    """List all available game adapter types with metadata."""
    try:
        all_adapters = AdapterRegistry.get_all_adapters()
        responses = []
        for adapter_type, adapter_cls in all_adapters.items():
            responses.append(
                AdapterInfoResponse(
                    adapter_type=adapter_type,
                    display_name=adapter_cls.DISPLAY_NAME,
                    game_name=adapter_cls.GAME_NAME,
                    supported_events=list(adapter_cls.SUPPORTED_EVENTS),
                    config_schema=_schema_to_fields(adapter_cls.get_config_schema()),
                    setup_instructions=adapter_cls.get_setup_instructions(),
                    supports_auto_setup=adapter_cls.supports_auto_setup(),
                )
            )
        return AdapterListResponse(adapters=responses, count=len(responses))
    except Exception as e:
        logger.error("Failed to list adapters: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


# ── Apply Preset ────────────────────────────────────────────────────────


@router.post(
    "/api/v1/game-integrations/{integration_id}/apply-preset",
    response_model=GameIntegrationResponse,
    tags=["Game Integration"],
)
async def apply_preset(
    integration_id: str,
    data: ApplyPresetRequest,
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
):
    """Apply a built-in preset to a game integration.

    If replace=true, replaces all existing mappings.
    If replace=false (default), appends preset mappings to existing ones.
    """
    from wled_controller.core.game_integration.presets import get_preset

    try:
        config = store.get_integration(integration_id)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"Game integration {integration_id} not found")

    preset = get_preset(data.preset_key)
    if preset is None:
        raise HTTPException(status_code=404, detail=f"Preset '{data.preset_key}' not found")

    if data.replace:
        new_mappings = list(preset.event_mappings)
    else:
        new_mappings = list(config.event_mappings) + list(preset.event_mappings)

    try:
        updated = store.update_integration(
            integration_id=integration_id,
            event_mappings=new_mappings,
        )
        fire_entity_event("game_integration", "updated", integration_id)
        return _config_to_response(updated)
    except Exception as e:
        logger.error("Failed to apply preset: %s", e, exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")


# ── Auto Setup ─────────────────────────────────────────────────────────


@router.post(
    "/api/v1/game-integrations/{integration_id}/auto-setup",
    response_model=AutoSetupResponse,
    tags=["Game Integration"],
)
async def auto_setup_integration(
    integration_id: str,
    request: Request,
    _auth: AuthRequired,
    store: GameIntegrationStore = Depends(get_game_integration_store),
):
    """Automatically write game config files for an integration.

    Detects the game installation and writes the GSI config file.
    Generates an auth token if not already set.
    """
    try:
        config = store.get_integration(integration_id)
    except EntityNotFoundError:
        raise HTTPException(status_code=404, detail=f"Game integration {integration_id} not found")

    # Look up adapter
    try:
        adapter_cls = AdapterRegistry.get_adapter(config.adapter_type)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    if not adapter_cls.supports_auto_setup():
        raise HTTPException(
            status_code=400,
            detail=f"Adapter '{config.adapter_type}' does not support auto setup",
        )

    # Determine server URL
    from wled_controller.api.routes.system_settings import load_external_url

    db = get_database()
    server_url = load_external_url(db)
    if not server_url:
        host = request.headers.get("host", "localhost:8080")
        server_url = f"http://{host}"

    # Run auto-setup
    try:
        result = adapter_cls.auto_setup(
            integration_id=integration_id,
            adapter_config=config.adapter_config,
            server_url=server_url,
        )
    except Exception as e:
        logger.error("Auto setup failed for %s: %s", integration_id, e, exc_info=True)
        raise HTTPException(status_code=500, detail=f"Auto setup failed: {e}")

    # If a new token was generated, persist the updated adapter_config
    if result.get("token_generated") and result.get("adapter_config"):
        try:
            store.update_integration(
                integration_id=integration_id,
                adapter_config=result["adapter_config"],
            )
            fire_entity_event("game_integration", "updated", integration_id)
        except Exception as e:
            logger.error(
                "Failed to save auto-generated token for %s: %s",
                integration_id,
                e,
            )

    return AutoSetupResponse(
        success=result.get("success", False),
        file_path=result.get("file_path", ""),
        message=result.get("message", ""),
        token_generated=result.get("token_generated", False),
    )
