"""
AutoPIL REST API
================
All /v1/* routes are tenant-scoped — the tenant is resolved from the API key.
Superadmin routes at /v1/admin/* require a key with is_superadmin=True.

Endpoints:
  POST   /v1/context/evaluate          — evaluate a context request
  GET    /v1/audit/sessions/{id}       — audit trail for a session
  GET    /v1/audit/events              — all events for this tenant
  GET    /v1/audit/stats               — dashboard stats for this tenant
  GET    /v1/policies                  — loaded policies (DB or YAML fallback)
  POST   /v1/policies/reload           — hot-reload policies from YAML into DB
  POST   /v1/policies                  — create a policy
  PUT    /v1/policies/{name}            — update a policy (saves version)
  DELETE /v1/policies/{name}            — delete a policy
  GET    /v1/policies/{name}/history    — version history for a policy
  POST   /v1/keys                      — create API key for this tenant
  GET    /v1/keys                      — list keys for this tenant
  DELETE /v1/keys/{key_id}             — revoke a key
  POST   /v1/admin/tenants             — create tenant (superadmin)
  GET    /v1/admin/tenants             — list all tenants (superadmin)
  DELETE /v1/admin/tenants/{id}        — deactivate tenant (superadmin)
  GET    /health                       — liveness check (no auth)
"""

import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional

import yaml
from fastapi import Depends, FastAPI, HTTPException, Query, Security
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.security import APIKeyHeader
from fastapi.staticfiles import StaticFiles

from autopil.db import create_stores
from autopil.models import AuditEvent, ContextRequest, Decision
from autopil.policy_engine import PolicyEngine

from .schemas import (
    AuditEventResponse,
    AuditTrailResponse,
    CreateKeyRequest,
    CreateKeyResponse,
    CreatePolicyRequest,
    CreateTenantRequest,
    CreateTenantResponse,
    EvaluateRequest,
    EvaluateResponse,
    HealthResponse,
    KeySummary,
    PolicyDetailResponse,
    PolicyVersionResponse,
    TenantResponse,
    UpdatePolicyRequest,
)

STATIC_DIR = Path(__file__).parent / "static"

_api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)


def create_app(
    policy_path: str,
    audit_db: str = "autopil_audit.db",
    database_url: str | None = None,
) -> FastAPI:
    app = FastAPI(
        title="AutoPIL",
        description="Platform Intelligence Layer — context governance for autonomous agents",
        version="0.1.0",
        docs_url="/docs",
        redoc_url="/redoc",
    )

    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_methods=["*"],
        allow_headers=["*"],
    )

    app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")

    # ── shared state ─────────────────────────────────────────────────────────
    audit_log, key_store, tenant_store, policy_store = create_stores(
        database_url=database_url, db_path=audit_db
    )
    state = {
        "policy_path":   policy_path,
        "audit_db":      database_url or audit_db,
        "engine":        PolicyEngine(policy_path),
        "log":           audit_log,
        "keys":          key_store,
        "tenants":       tenant_store,
        "policy_store":  policy_store,
    }

    # ── auth dependencies ─────────────────────────────────────────────────────

    def require_api_key(raw: str = Security(_api_key_header)) -> dict:
        if not raw:
            raise HTTPException(status_code=401, detail="Missing API key. Set X-API-Key header.")
        info = state["keys"].validate(raw)
        if not info:
            raise HTTPException(status_code=403, detail="Invalid or revoked API key.")
        return info  # {"key_id", "tenant_id", "is_superadmin"}

    def require_superadmin(key_info: dict = Depends(require_api_key)) -> dict:
        if not key_info.get("is_superadmin"):
            raise HTTPException(status_code=403, detail="Superadmin key required.")
        return key_info

    # ── policy engine helper ──────────────────────────────────────────────────

    def _get_engine(tenant_id: str) -> PolicyEngine:
        db_policies = state["policy_store"].list_active(tenant_id)
        if db_policies:
            return PolicyEngine.from_list(db_policies)
        return state["engine"]

    # ── dashboard ─────────────────────────────────────────────────────────────

    @app.get("/", include_in_schema=False)
    def dashboard():
        return FileResponse(str(STATIC_DIR / "index.html"))

    # ── health ────────────────────────────────────────────────────────────────

    @app.get("/health", response_model=HealthResponse, tags=["System"])
    def health():
        return HealthResponse(
            status="ok", version="0.1.0",
            policy_file=state["policy_path"], audit_db=state["audit_db"],
        )

    # ── tenant management (superadmin only) ───────────────────────────────────

    @app.post("/v1/admin/tenants", response_model=CreateTenantResponse, tags=["Admin"])
    def create_tenant(req: CreateTenantRequest, _: dict = Depends(require_superadmin)):
        """Create a new tenant and return its initial admin API key."""
        try:
            tenant_id = state["tenants"].create_tenant(req.name)
        except Exception as exc:
            if "UNIQUE" in str(exc).upper() or "unique" in str(exc).lower():
                raise HTTPException(status_code=409, detail=f"Tenant name '{req.name}' already exists.")
            raise
        _, admin_key = state["keys"].create_key("admin", tenant_id=tenant_id)
        tenant = state["tenants"].get_tenant(tenant_id)
        return CreateTenantResponse(
            tenant_id=tenant_id,
            name=req.name,
            created_at=tenant["created_at"],
            admin_key=admin_key,
        )

    @app.get("/v1/admin/tenants", response_model=list[TenantResponse], tags=["Admin"])
    def list_tenants(_: dict = Depends(require_superadmin)):
        """List all tenants."""
        return [TenantResponse(**t) for t in state["tenants"].list_tenants()]

    @app.delete("/v1/admin/tenants/{tenant_id}", tags=["Admin"])
    def deactivate_tenant(tenant_id: str, _: dict = Depends(require_superadmin)):
        """Deactivate a tenant. Their keys and audit data are retained."""
        if not state["tenants"].deactivate_tenant(tenant_id):
            raise HTTPException(status_code=404, detail=f"Tenant '{tenant_id}' not found.")
        return {"status": "deactivated", "tenant_id": tenant_id}

    # ── API key management ────────────────────────────────────────────────────

    @app.post("/v1/keys", response_model=CreateKeyResponse, tags=["Keys"])
    def create_key(req: CreateKeyRequest, key_info: dict = Depends(require_api_key)):
        """Create a new API key scoped to the calling tenant."""
        tenant_id = key_info["tenant_id"]
        key_id, plaintext = state["keys"].create_key(req.name, tenant_id=tenant_id)
        keys = state["keys"].list_keys(tenant_id)
        created_at = next(k["created_at"] for k in keys if k["key_id"] == key_id)
        return CreateKeyResponse(key_id=key_id, name=req.name, key=plaintext, created_at=created_at)

    @app.get("/v1/keys", response_model=list[KeySummary], tags=["Keys"])
    def list_keys(key_info: dict = Depends(require_api_key)):
        """List all keys for the calling tenant."""
        return [KeySummary(**k) for k in state["keys"].list_keys(key_info["tenant_id"])]

    @app.delete("/v1/keys/{key_id}", tags=["Keys"])
    def revoke_key(key_id: str, key_info: dict = Depends(require_api_key)):
        """Revoke a key. Only keys belonging to the calling tenant can be revoked."""
        if not state["keys"].revoke(key_id, key_info["tenant_id"]):
            raise HTTPException(status_code=404, detail=f"Key '{key_id}' not found or already revoked.")
        return {"status": "revoked", "key_id": key_id}

    # ── context evaluation ────────────────────────────────────────────────────

    @app.post("/v1/context/evaluate", response_model=EvaluateResponse, tags=["Context"])
    def evaluate(req: EvaluateRequest, key_info: dict = Depends(require_api_key)):
        tenant_id = key_info["tenant_id"]
        request = ContextRequest(
            query=req.query, agent_role=req.agent_role, user_id=req.user_id,
            source_id=req.source_id, sensitivity_level=req.sensitivity_level,
            session_id=req.session_id,
        )
        decision, policy_name, reason = _get_engine(tenant_id).evaluate(request)
        event = AuditEvent(
            event_id=str(uuid.uuid4()), session_id=req.session_id,
            agent_role=req.agent_role, user_id=req.user_id, source_id=req.source_id,
            query=req.query, decision=decision, policy_name=policy_name,
            reason=reason, timestamp=datetime.utcnow(),
        )
        state["log"].record(event, tenant_id=tenant_id)
        return EvaluateResponse(
            decision=decision, policy_name=policy_name, reason=reason,
            event_id=event.event_id, session_id=req.session_id, timestamp=event.timestamp,
        )

    # ── audit trail ───────────────────────────────────────────────────────────

    @app.get("/v1/audit/sessions/{session_id}", response_model=AuditTrailResponse, tags=["Audit"])
    def get_session_audit(session_id: str, key_info: dict = Depends(require_api_key)):
        tenant_id = key_info["tenant_id"]
        events = state["log"].get_session_events(session_id, tenant_id)
        if not events:
            raise HTTPException(status_code=404, detail=f"No events found for session '{session_id}'")
        return AuditTrailResponse(
            session_id=session_id, total_events=len(events),
            allowed=sum(1 for e in events if e.decision == Decision.ALLOW),
            denied=sum(1 for e in events if e.decision == Decision.DENY),
            events=[_to_response(e) for e in events],
        )

    @app.get("/v1/audit/stats", tags=["Audit"])
    def get_stats(key_info: dict = Depends(require_api_key)):
        tenant_id = key_info["tenant_id"]
        events   = state["log"].get_all_events(tenant_id)
        sessions = list({e.session_id for e in events})
        allowed  = [e for e in events if e.decision == Decision.ALLOW]
        denied   = [e for e in events if e.decision == Decision.DENY]
        roles, sources = {}, {}
        for e in events:
            roles[e.agent_role]  = roles.get(e.agent_role, 0) + 1
            sources[e.source_id] = sources.get(e.source_id, 0) + 1
        return {
            "total_events":    len(events),
            "allowed":         len(allowed),
            "denied":          len(denied),
            "active_sessions": len(sessions),
            "top_roles":       sorted(roles.items(),   key=lambda x: -x[1])[:5],
            "top_sources":     sorted(sources.items(), key=lambda x: -x[1])[:5],
            "recent_events":   [_to_response(e) for e in reversed(events[-10:])],
        }

    @app.get("/v1/audit/events", response_model=list[AuditEventResponse], tags=["Audit"])
    def get_all_events(
        decision:   Optional[Decision] = Query(default=None),
        agent_role: Optional[str]      = Query(default=None),
        limit:      int                = Query(default=100, le=1000),
        key_info:   dict               = Depends(require_api_key),
    ):
        tenant_id = key_info["tenant_id"]
        events = state["log"].get_all_events(tenant_id)
        if decision:
            events = [e for e in events if e.decision == decision]
        if agent_role:
            events = [e for e in events if e.agent_role == agent_role]
        return [_to_response(e) for e in events[:limit]]

    # ── policy management ─────────────────────────────────────────────────────

    @app.get("/v1/policies", response_model=list[PolicyDetailResponse], tags=["Policies"])
    def list_policies(key_info: dict = Depends(require_api_key)):
        tenant_id = key_info["tenant_id"]
        db_policies = state["policy_store"].list_active(tenant_id)
        if db_policies:
            return [PolicyDetailResponse(**p) for p in db_policies]
        # fallback: read from YAML and return as PolicyDetailResponse with synthetic metadata
        with open(state["policy_path"]) as f:
            data = yaml.safe_load(f)
        return [
            PolicyDetailResponse(
                policy_id="",
                name=p["name"],
                agent_role=p["agent_role"],
                allowed_sources=p.get("allowed_sources", []),
                denied_sources=p.get("denied_sources", []),
                max_sensitivity=p.get("max_sensitivity", "restricted"),
                version=0,
                created_at="",
                updated_at="",
            )
            for p in data.get("policies", [])
        ]

    @app.post("/v1/policies/reload", tags=["Policies"])
    def reload_policies(key_info: dict = Depends(require_api_key)):
        tenant_id = key_info["tenant_id"]
        state["engine"] = PolicyEngine(state["policy_path"])
        with open(state["policy_path"]) as f:
            data = yaml.safe_load(f)
        policies = data.get("policies", [])
        # Import into DB for this tenant
        for p in policies:
            state["policy_store"].upsert(
                {
                    "name": p["name"],
                    "agent_role": p["agent_role"],
                    "allowed_sources": p.get("allowed_sources", []),
                    "denied_sources": p.get("denied_sources", []),
                    "max_sensitivity": p.get("max_sensitivity", "restricted"),
                },
                tenant_id=tenant_id,
                changed_by=key_info["key_id"],
            )
        return {"status": "reloaded", "policies_loaded": len(policies)}

    @app.post("/v1/policies", response_model=PolicyDetailResponse, tags=["Policies"])
    def create_policy(req: CreatePolicyRequest, key_info: dict = Depends(require_api_key)):
        """Create a new policy for this tenant."""
        tenant_id = key_info["tenant_id"]
        if state["policy_store"].get(req.name, tenant_id):
            raise HTTPException(status_code=409, detail=f"Policy '{req.name}' already exists.")
        state["policy_store"].upsert(req.model_dump(), tenant_id=tenant_id, changed_by=key_info["key_id"])
        policy = state["policy_store"].get(req.name, tenant_id)
        return PolicyDetailResponse(**policy)

    @app.put("/v1/policies/{name}", response_model=PolicyDetailResponse, tags=["Policies"])
    def update_policy(name: str, req: UpdatePolicyRequest, key_info: dict = Depends(require_api_key)):
        """Update an existing policy. Saves the previous version to history."""
        tenant_id = key_info["tenant_id"]
        if not state["policy_store"].get(name, tenant_id):
            raise HTTPException(status_code=404, detail=f"Policy '{name}' not found.")
        state["policy_store"].upsert(
            {"name": name, **req.model_dump()},
            tenant_id=tenant_id,
            changed_by=key_info["key_id"],
        )
        policy = state["policy_store"].get(name, tenant_id)
        return PolicyDetailResponse(**policy)

    @app.delete("/v1/policies/{name}", tags=["Policies"])
    def delete_policy(name: str, key_info: dict = Depends(require_api_key)):
        """Soft-delete a policy."""
        tenant_id = key_info["tenant_id"]
        if not state["policy_store"].delete(name, tenant_id):
            raise HTTPException(status_code=404, detail=f"Policy '{name}' not found.")
        return {"status": "deleted", "name": name}

    @app.get("/v1/policies/{name}/history", response_model=list[PolicyVersionResponse], tags=["Policies"])
    def get_policy_history(name: str, key_info: dict = Depends(require_api_key)):
        """Return version history for a policy, newest first."""
        tenant_id = key_info["tenant_id"]
        history = state["policy_store"].get_history(name, tenant_id)
        if not history:
            raise HTTPException(status_code=404, detail=f"No history found for policy '{name}'.")
        return [PolicyVersionResponse(**v) for v in history]

    # ── helpers ───────────────────────────────────────────────────────────────

    def _to_response(e: AuditEvent) -> AuditEventResponse:
        return AuditEventResponse(
            event_id=e.event_id, session_id=e.session_id, agent_role=e.agent_role,
            user_id=e.user_id, source_id=e.source_id, query=e.query,
            decision=e.decision, policy_name=e.policy_name, reason=e.reason,
            timestamp=e.timestamp, context_hash=e.context_hash,
        )

    return app
