"""Matrix API endpoints — config, agents, health, thoughts, memory search."""

import logging
import time
from pathlib import Path
from typing import Any

import yaml
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse

from config import settings
from timmy.memory_system import search_memories

from .utils import (
    _DEFAULT_STATUS,
    _compute_circular_positions,
    _get_agent_color,
    _get_agent_shape,
    _get_client_ip,
)

logger = logging.getLogger(__name__)

matrix_router = APIRouter(prefix="/api/matrix", tags=["matrix"])

# Default Matrix configuration (fallback when matrix.yaml is missing/corrupt)
_DEFAULT_MATRIX_CONFIG: dict[str, Any] = {
    "lighting": {
        "ambient_color": "#1a1a2e",
        "ambient_intensity": 0.4,
        "point_lights": [
            {"color": "#FFD700", "intensity": 1.2, "position": {"x": 0, "y": 5, "z": 0}},
            {"color": "#3B82F6", "intensity": 0.8, "position": {"x": -5, "y": 3, "z": -5}},
            {"color": "#A855F7", "intensity": 0.6, "position": {"x": 5, "y": 3, "z": 5}},
        ],
    },
    "environment": {
        "rain_enabled": False,
        "starfield_enabled": True,
        "fog_color": "#0f0f23",
        "fog_density": 0.02,
    },
    "features": {
        "chat_enabled": True,
        "visitor_avatars": True,
        "pip_familiar": True,
        "workshop_portal": True,
    },
}


def _load_matrix_config() -> dict[str, Any]:
    """Load Matrix world configuration from matrix.yaml with fallback to defaults.

    Returns a dict with sections: lighting, environment, features.
    If the config file is missing or invalid, returns sensible defaults.
    """
    try:
        config_path = Path(settings.repo_root) / "config" / "matrix.yaml"
        if not config_path.exists():
            logger.debug("matrix.yaml not found, using default config")
            return _DEFAULT_MATRIX_CONFIG.copy()

        raw = config_path.read_text()
        config = yaml.safe_load(raw)
        if not isinstance(config, dict):
            logger.warning("matrix.yaml invalid format, using defaults")
            return _DEFAULT_MATRIX_CONFIG.copy()

        # Merge with defaults to ensure all required fields exist
        result: dict[str, Any] = {
            "lighting": {
                **_DEFAULT_MATRIX_CONFIG["lighting"],
                **config.get("lighting", {}),
            },
            "environment": {
                **_DEFAULT_MATRIX_CONFIG["environment"],
                **config.get("environment", {}),
            },
            "features": {
                **_DEFAULT_MATRIX_CONFIG["features"],
                **config.get("features", {}),
            },
        }

        # Ensure point_lights is a list
        if "point_lights" in config.get("lighting", {}):
            result["lighting"]["point_lights"] = config["lighting"]["point_lights"]
        else:
            result["lighting"]["point_lights"] = _DEFAULT_MATRIX_CONFIG["lighting"]["point_lights"]

        return result
    except Exception as exc:
        logger.warning("Failed to load matrix config: %s, using defaults", exc)
        return _DEFAULT_MATRIX_CONFIG.copy()


@matrix_router.get("/config")
async def get_matrix_config() -> JSONResponse:
    """Return Matrix world configuration.

    Serves lighting presets, environment settings, and feature flags
    to the Matrix frontend so it can be config-driven rather than
    hardcoded. Reads from config/matrix.yaml with sensible defaults.

    """
    config = _load_matrix_config()
    return JSONResponse(
        content=config,
        headers={"Cache-Control": "no-cache, no-store"},
    )


def _build_matrix_agents_response() -> list[dict[str, Any]]:
    """Build the Matrix agent registry response.

    Reads from agents.yaml and returns agents with Matrix-compatible
    formatting including colors, shapes, and positions.
    """
    try:
        from timmy.agents.loader import list_agents

        agents = list_agents()
        if not agents:
            return []

        positions = _compute_circular_positions(len(agents))

        result = []
        for i, agent in enumerate(agents):
            agent_id = agent.get("id", "")
            result.append(
                {
                    "id": agent_id,
                    "display_name": agent.get("name", agent_id.title()),
                    "role": agent.get("role", "general"),
                    "color": _get_agent_color(agent_id),
                    "position": positions[i],
                    "shape": _get_agent_shape(agent_id),
                    "status": agent.get("status", _DEFAULT_STATUS),
                }
            )

        return result
    except Exception as exc:
        logger.warning("Failed to load agents for Matrix: %s", exc)
        return []


@matrix_router.get("/agents")
async def get_matrix_agents() -> JSONResponse:
    """Return the agent registry for Matrix visualization.

    Serves agents from agents.yaml with Matrix-compatible formatting.
    Returns 200 with empty list if no agents configured.
    """
    agents = _build_matrix_agents_response()
    return JSONResponse(
        content=agents,
        headers={"Cache-Control": "no-cache, no-store"},
    )


_MAX_THOUGHT_LIMIT = 50  # Maximum thoughts allowed per request
_DEFAULT_THOUGHT_LIMIT = 10  # Default number of thoughts to return
_MAX_THOUGHT_TEXT_LEN = 500  # Max characters for thought text


def _build_matrix_thoughts_response(limit: int = _DEFAULT_THOUGHT_LIMIT) -> list[dict[str, Any]]:
    """Build the Matrix thoughts response from the thinking engine.

    Returns recent thoughts formatted for Matrix display:
    - id: thought UUID
    - text: thought content (truncated to 500 chars)
    - created_at: ISO-8601 timestamp
    - chain_id: parent thought ID (or null if root thought)

    Returns empty list if thinking engine is disabled or fails.
    """
    try:
        from timmy.thinking import thinking_engine

        thoughts = thinking_engine.get_recent_thoughts(limit=limit)
        return [
            {
                "id": t.id,
                "text": t.content[:_MAX_THOUGHT_TEXT_LEN],
                "created_at": t.created_at,
                "chain_id": t.parent_id,
            }
            for t in thoughts
        ]
    except Exception as exc:
        logger.warning("Failed to load thoughts for Matrix: %s", exc)
        return []


@matrix_router.get("/thoughts")
async def get_matrix_thoughts(limit: int = _DEFAULT_THOUGHT_LIMIT) -> JSONResponse:
    """Return Timmy's recent thoughts formatted for Matrix display.

    Query params:
        - limit: Number of thoughts to return (default 10, max 50)

    Returns empty array if thinking engine is disabled or fails.
    """
    # Clamp limit to valid range
    if limit < 1:
        limit = 1
    elif limit > _MAX_THOUGHT_LIMIT:
        limit = _MAX_THOUGHT_LIMIT

    thoughts = _build_matrix_thoughts_response(limit=limit)
    return JSONResponse(
        content=thoughts,
        headers={"Cache-Control": "no-cache, no-store"},
    )


# Health check cache (5-second TTL for capability checks)
_health_cache: dict | None = None
_health_cache_ts: float = 0.0
_HEALTH_CACHE_TTL = 5.0


def _check_capability_thinking() -> bool:
    """Check if thinking engine is available."""
    try:
        from timmy.thinking import thinking_engine

        # Check if the engine has been initialized (has a db path)
        return hasattr(thinking_engine, "_db") and thinking_engine._db is not None
    except Exception:
        return False


def _check_capability_memory() -> bool:
    """Check if memory system is available."""
    try:
        from timmy.memory_system import HOT_MEMORY_PATH

        return HOT_MEMORY_PATH.exists()
    except Exception:
        return False


def _check_capability_bark() -> bool:
    """Check if bark production is available."""
    try:
        from infrastructure.presence import produce_bark

        return callable(produce_bark)
    except Exception:
        return False


def _check_capability_familiar() -> bool:
    """Check if familiar (Pip) is available."""
    try:
        from timmy.familiar import pip_familiar

        return pip_familiar is not None
    except Exception:
        return False


def _check_capability_lightning() -> bool:
    """Check if Lightning payments are available."""
    # Lightning is currently disabled per health.py
    # Returns False until properly re-implemented
    return False


def _build_matrix_health_response() -> dict[str, Any]:
    """Build the Matrix health response with capability checks.

    Performs lightweight checks (<100ms total) to determine which features
    are available. Returns 200 even if some capabilities are degraded.
    """
    capabilities = {
        "thinking": _check_capability_thinking(),
        "memory": _check_capability_memory(),
        "bark": _check_capability_bark(),
        "familiar": _check_capability_familiar(),
        "lightning": _check_capability_lightning(),
    }

    # Status is ok if core capabilities (thinking, memory, bark) are available
    core_caps = ["thinking", "memory", "bark"]
    core_available = all(capabilities[c] for c in core_caps)
    status = "ok" if core_available else "degraded"

    return {
        "status": status,
        "version": "1.0.0",
        "capabilities": capabilities,
    }


@matrix_router.get("/health")
async def get_matrix_health() -> JSONResponse:
    """Return health status and capability availability for Matrix frontend.

    Returns 200 even if some capabilities are degraded.
    """
    response = _build_matrix_health_response()
    status_code = 200  # Always 200, even if degraded

    return JSONResponse(
        content=response,
        status_code=status_code,
        headers={"Cache-Control": "no-cache, no-store"},
    )


# Rate limiting: 1 search per 5 seconds per IP
_MEMORY_SEARCH_RATE_LIMIT_SECONDS = 5
_memory_search_last_request: dict[str, float] = {}
_MAX_MEMORY_RESULTS = 5
_MAX_MEMORY_TEXT_LENGTH = 200


def _build_matrix_memory_response(
    memories: list,
) -> list[dict[str, Any]]:
    """Build the Matrix memory search response.

    Formats memory entries for Matrix display:
    - text: truncated to 200 characters
    - relevance: 0-1 score from relevance_score
    - created_at: ISO-8601 timestamp
    - context_type: the memory type

    Results are capped at _MAX_MEMORY_RESULTS.
    """
    results = []
    for mem in memories[:_MAX_MEMORY_RESULTS]:
        text = mem.content
        if len(text) > _MAX_MEMORY_TEXT_LENGTH:
            text = text[:_MAX_MEMORY_TEXT_LENGTH] + "..."

        results.append(
            {
                "text": text,
                "relevance": round(mem.relevance_score or 0.0, 4),
                "created_at": mem.timestamp,
                "context_type": mem.context_type,
            }
        )
    return results


@matrix_router.get("/memory/search")
async def get_matrix_memory_search(request: Request, q: str | None = None) -> JSONResponse:
    """Search Timmy's memory for relevant snippets.

    Rate limited to 1 search per 5 seconds per IP.
    Returns 200 with results, 400 if missing query, or 429 if rate limited.
    """
    # Validate query parameter
    query = q.strip() if q else ""
    if not query:
        return JSONResponse(
            status_code=400,
            content={"error": "Query parameter 'q' is required"},
        )

    # Rate limiting check by IP
    client_ip = _get_client_ip(request)
    now = time.time()
    last_request = _memory_search_last_request.get(client_ip, 0)
    time_since_last = now - last_request

    if time_since_last < _MEMORY_SEARCH_RATE_LIMIT_SECONDS:
        retry_after = _MEMORY_SEARCH_RATE_LIMIT_SECONDS - time_since_last
        return JSONResponse(
            status_code=429,
            content={"error": "Rate limit exceeded. Try again later."},
            headers={"Retry-After": str(int(retry_after) + 1)},
        )

    # Record this request
    _memory_search_last_request[client_ip] = now

    # Search memories
    try:
        memories = search_memories(query, limit=_MAX_MEMORY_RESULTS)
        results = _build_matrix_memory_response(memories)
    except Exception as exc:
        logger.warning("Memory search failed: %s", exc)
        results = []

    return JSONResponse(
        content=results,
        headers={"Cache-Control": "no-cache, no-store"},
    )
