"""Dashboard REST API endpoints."""

from __future__ import annotations

import asyncio
import hashlib
import logging

from aiohttp import web

from backend.config import save_config
from backend.db import queries
from backend.db.connection import get_db

logger = logging.getLogger("ecate.dashboard")

# Lock to prevent concurrent calibration starts
_calibration_lock = asyncio.Lock()


async def _check_api_key(request: web.Request) -> bool:
    """Validate API key for protected endpoints. Returns True if valid."""
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer "):
        return False
    
    token = auth_header[7:].strip()
    if not token:
        return False
    
    key_hash = hashlib.sha256(token.encode()).hexdigest()
    db = await get_db()
    rows = await db.execute_fetchall(
        "SELECT id FROM api_keys WHERE key_hash = ? AND is_active = TRUE",
        (key_hash,),
    )
    return bool(rows)


# ── Overview & Analytics ──────────────────────────────────────────────────────

async def handle_overview(request: web.Request) -> web.Response:
    days = int(request.query.get("days", "30"))
    data = await queries.get_overview(days)
    return web.json_response(data)


async def handle_cost_by_model(request: web.Request) -> web.Response:
    days = int(request.query.get("days", "30"))
    data = await queries.get_cost_by_model(days)
    return web.json_response(data)


async def handle_cost_over_time(request: web.Request) -> web.Response:
    days = int(request.query.get("days", "30"))
    data = await queries.get_cost_over_time(days)
    return web.json_response(data)


async def handle_recent_requests(request: web.Request) -> web.Response:
    limit = int(request.query.get("limit", "50"))
    data = await queries.get_recent_requests(limit)
    return web.json_response(data)


async def handle_savings_estimate(request: web.Request) -> web.Response:
    days = int(request.query.get("days", "30"))
    data = await queries.get_savings_estimate(days)
    return web.json_response(data)


async def handle_health(request: web.Request) -> web.Response:
    circuit_breaker = request.app.get("circuit_breaker")
    return web.json_response({
        "status": "healthy",
        "service": "ecate",
        "circuit_breakers": circuit_breaker.get_status() if circuit_breaker else {},
    })


# ── Model Configuration (CRUD) ───────────────────────────────────────────────

async def handle_get_models(request: web.Request) -> web.Response:
    """GET /api/models — list all configured models."""
    config = request.app["config"]
    models = []
    for provider in config.providers:
        for model in provider.models:
            models.append({
                "provider_id": provider.id,
                "provider_type": provider.type,
                "provider_base_url": provider.base_url,
                "model_id": model.id,
                "full_id": f"{provider.id}/{model.id}",
                "input_cost_per_mtok": model.input_cost_per_mtok,
                "output_cost_per_mtok": model.output_cost_per_mtok,
                "cache_read_cost_per_mtok": model.cache_read_cost_per_mtok,
                "cache_creation_cost_per_mtok": model.cache_creation_cost_per_mtok,
                "max_context": model.max_context,
                "supports_tools": model.supports_tools,
                "supports_vision": model.supports_vision,
                "calibration_enabled": model.calibration_enabled,
                "calibration_samples": model.calibration_samples,
            })
    return web.json_response(models)


async def handle_add_model(request: web.Request) -> web.Response:
    """POST /api/models — add a model to an existing or new provider."""
    from backend.config import ModelConfig, ProviderConfig

    try:
        body = await request.json()
    except Exception:
        raise web.HTTPBadRequest(text="Invalid JSON")

    provider_id = body.get("provider_id")
    provider_type = body.get("provider_type", "openai")
    provider_base_url = body.get("provider_base_url", "")
    provider_api_key = body.get("provider_api_key", "")
    model_id = body.get("model_id")

    if not provider_id or not model_id:
        raise web.HTTPBadRequest(text="provider_id and model_id are required")

    config = request.app["config"]

    new_model = ModelConfig(
        id=model_id,
        input_cost_per_mtok=float(body.get("input_cost_per_mtok", 0)),
        output_cost_per_mtok=float(body.get("output_cost_per_mtok", 0)),
        cache_read_cost_per_mtok=float(body.get("cache_read_cost_per_mtok", 0)),
        cache_creation_cost_per_mtok=float(body.get("cache_creation_cost_per_mtok", 0)),
        max_context=int(body.get("max_context", 128000)),
        supports_tools=bool(body.get("supports_tools", False)),
        supports_vision=bool(body.get("supports_vision", False)),
    )

    # Find or create provider
    provider = None
    for p in config.providers:
        if p.id == provider_id:
            provider = p
            break

    if provider is None:
        if not provider_base_url:
            raise web.HTTPBadRequest(text="provider_base_url required for new provider")
        provider = ProviderConfig(
            id=provider_id,
            type=provider_type,
            base_url=provider_base_url,
            api_key=provider_api_key,
            models=[],
        )
        config.providers.append(provider)

    # Check for duplicate model
    for m in provider.models:
        if m.id == model_id:
            raise web.HTTPConflict(text=f"Model {model_id} already exists in provider {provider_id}")

    provider.models.append(new_model)

    # Persist to config file
    try:
        save_config(config)
        logger.info(f"Added model {provider_id}/{model_id} and saved config")
    except Exception as e:
        logger.error(f"Failed to save config: {e}")

    return web.json_response({"status": "ok", "model": f"{provider_id}/{model_id}"}, status=201)


async def handle_update_model(request: web.Request) -> web.Response:
    """PUT /api/models/{provider_id}/{model_id} — update model config."""
    provider_id = request.match_info["provider_id"]
    model_id = request.match_info["model_id"]

    try:
        body = await request.json()
    except Exception:
        raise web.HTTPBadRequest(text="Invalid JSON")

    config = request.app["config"]

    for provider in config.providers:
        if provider.id == provider_id:
            for i, model in enumerate(provider.models):
                if model.id == model_id:
                    # Update fields
                    if "input_cost_per_mtok" in body:
                        model.input_cost_per_mtok = float(body["input_cost_per_mtok"])
                    if "output_cost_per_mtok" in body:
                        model.output_cost_per_mtok = float(body["output_cost_per_mtok"])
                    if "cache_read_cost_per_mtok" in body:
                        model.cache_read_cost_per_mtok = float(body["cache_read_cost_per_mtok"])
                    if "cache_creation_cost_per_mtok" in body:
                        model.cache_creation_cost_per_mtok = float(body["cache_creation_cost_per_mtok"])
                    if "max_context" in body:
                        model.max_context = int(body["max_context"])
                    if "supports_tools" in body:
                        model.supports_tools = bool(body["supports_tools"])
                    if "supports_vision" in body:
                        model.supports_vision = bool(body["supports_vision"])
                    
                    # Persist to config file
                    try:
                        save_config(config)
                        logger.info(f"Updated model {provider_id}/{model_id} and saved config")
                    except Exception as e:
                        logger.error(f"Failed to save config: {e}")
                    
                    return web.json_response({"status": "ok"})

    raise web.HTTPNotFound(text=f"Model {provider_id}/{model_id} not found")


async def handle_delete_model(request: web.Request) -> web.Response:
    """DELETE /api/models/{provider_id}/{model_id} — remove a model."""
    provider_id = request.match_info["provider_id"]
    model_id = request.match_info["model_id"]

    config = request.app["config"]

    for provider in config.providers:
        if provider.id == provider_id:
            for i, model in enumerate(provider.models):
                if model.id == model_id:
                    provider.models.pop(i)
                    # Remove provider if no models left
                    if not provider.models:
                        config.providers.remove(provider)
                    
                    # Persist to config file
                    try:
                        save_config(config)
                        logger.info(f"Deleted model {provider_id}/{model_id} and saved config")
                    except Exception as e:
                        logger.error(f"Failed to save config: {e}")
                    
                    return web.json_response({"status": "ok"})

    raise web.HTTPNotFound(text=f"Model {provider_id}/{model_id} not found")


# ── Calibration ───────────────────────────────────────────────────────────────

async def handle_start_calibration(request: web.Request) -> web.Response:
    """POST /api/calibration/start — trigger a calibration run.
    
    Requires valid API key — calibration is expensive.
    
    Body:
        model_ids: Optional list of model IDs to calibrate (uses calibration_enabled if not specified)
    """
    from backend.calibration.runner import get_progress, run_calibration

    # Validate API key
    if not await _check_api_key(request):
        raise web.HTTPUnauthorized(text="Valid API key required to start calibration")

    # Use lock to prevent race condition between check and task creation
    async with _calibration_lock:
        progress = get_progress()
        if progress.status == "running":
            return web.json_response({"status": "already_running", "progress": _progress_dict(progress)}, status=409)

        body = {}
        try:
            body = await request.json()
        except Exception:
            pass

        model_ids = body.get("model_ids")  # Optional: filter to specific models
        config = request.app["config"]
        session = request.app["http_session"]

        # Run calibration in background (status set to "running" inside run_calibration)
        asyncio.create_task(run_calibration(config, session, model_ids))

    return web.json_response({"status": "started"})


async def handle_get_model_vectors(request: web.Request) -> web.Response:
    """GET /api/routing/vectors — get learned model vectors info."""
    from pathlib import Path
    import json
    
    config = request.app["config"]
    vectors_path = Path(config.routing.vectors_path)
    
    if not vectors_path.exists():
        return web.json_response({"vectors": {}, "embed_dim": 512})
    
    try:
        with open(vectors_path) as f:
            data = json.load(f)
        # Don't send full vectors (too large), just metadata
        return web.json_response({
            "version": data.get("version", 1),
            "embed_dim": data.get("embed_dim", 512),
            "embedder_model": data.get("embedder_model", ""),
            "models": list(data.get("vectors", {}).keys()),
        })
    except Exception as e:
        logger.error(f"Failed to load vectors: {e}")
        return web.json_response({"error": str(e)}, status=500)


async def handle_update_model_calibration(request: web.Request) -> web.Response:
    """PUT /api/models/{provider_id}/{model_id}/calibration — update model calibration settings."""
    provider_id = request.match_info["provider_id"]
    model_id = request.match_info["model_id"]

    try:
        body = await request.json()
    except Exception:
        raise web.HTTPBadRequest(text="Invalid JSON")

    config = request.app["config"]

    for provider in config.providers:
        if provider.id == provider_id:
            for model in provider.models:
                if model.id == model_id:
                    if "calibration_enabled" in body:
                        model.calibration_enabled = bool(body["calibration_enabled"])
                    if "calibration_samples" in body:
                        model.calibration_samples = int(body["calibration_samples"])
                    
                    try:
                        save_config(config)
                        logger.info(f"Updated calibration settings for {provider_id}/{model_id}")
                    except Exception as e:
                        logger.error(f"Failed to save config: {e}")
                    
                    return web.json_response({"status": "ok"})

    raise web.HTTPNotFound(text=f"Model {provider_id}/{model_id} not found")


async def handle_calibration_progress(request: web.Request) -> web.Response:
    """GET /api/calibration/progress — get current calibration status."""
    from backend.calibration.runner import get_progress
    progress = get_progress()
    return web.json_response(_progress_dict(progress))


async def handle_calibration_runs(request: web.Request) -> web.Response:
    """GET /api/calibration/runs — list past calibration runs."""
    from backend.db.connection import get_db
    db = await get_db()
    rows = await db.execute_fetchall(
        "SELECT id, started_at, completed_at, judge_model, models_tested, tasks_count, total_cost_usd, status "
        "FROM calibration_runs ORDER BY id DESC LIMIT 20"
    )
    return web.json_response([dict(r) for r in rows])


async def handle_calibration_results(request: web.Request) -> web.Response:
    """GET /api/calibration/runs/{run_id}/results — get results for a calibration run."""
    run_id = int(request.match_info["run_id"])
    from backend.db.connection import get_db
    db = await get_db()
    rows = await db.execute_fetchall(
        """SELECT model_id, task_id, category, correctness, completeness,
                  following, composite_score, latency_total_ms, tokens_in, tokens_out,
                  cost_usd, judge_reasoning
           FROM calibration_results WHERE run_id = ? ORDER BY model_id, task_id""",
        (run_id,),
    )
    return web.json_response([dict(r) for r in rows])


def _progress_dict(progress) -> dict:
    return {
        "status": progress.status,
        "total_tasks": progress.total_tasks,
        "completed_tasks": progress.completed_tasks,
        "current_model": progress.current_model,
        "current_task": progress.current_task,
        "run_id": progress.run_id,
        "error": progress.error,
        "pct": round(progress.completed_tasks / max(progress.total_tasks, 1) * 100, 1),
    }


# ── Route Registration ────────────────────────────────────────────────────────

def register_dashboard_routes(app: web.Application):
    """Register all dashboard API routes."""
    # Analytics
    app.router.add_get("/api/overview", handle_overview)
    app.router.add_get("/api/cost/by-model", handle_cost_by_model)
    app.router.add_get("/api/cost/over-time", handle_cost_over_time)
    app.router.add_get("/api/cost/savings", handle_savings_estimate)
    app.router.add_get("/api/requests", handle_recent_requests)
    app.router.add_get("/health", handle_health)

    # Model CRUD
    app.router.add_get("/api/models", handle_get_models)
    app.router.add_post("/api/models", handle_add_model)
    app.router.add_put("/api/models/{provider_id}/{model_id}", handle_update_model)
    app.router.add_delete("/api/models/{provider_id}/{model_id}", handle_delete_model)

    # Calibration
    app.router.add_post("/api/calibration/start", handle_start_calibration)
    app.router.add_get("/api/calibration/progress", handle_calibration_progress)
    app.router.add_get("/api/calibration/runs", handle_calibration_runs)
    app.router.add_get("/api/calibration/runs/{run_id}/results", handle_calibration_results)
    
    # Routing vectors
    app.router.add_get("/api/routing/vectors", handle_get_model_vectors)
    app.router.add_put("/api/models/{provider_id}/{model_id}/calibration", handle_update_model_calibration)
