"""
BIBIVIDI — Dashboard API router
REST endpoints consumed by /bibividi/ui (React SPA).
Mounted into the main FastAPI app in proxy.py.
"""
from __future__ import annotations

from typing import Any

from fastapi import APIRouter, HTTPException, Query
from fastapi.responses import Response

from bvd.config import settings
from bvd.engine.library import Script, library
from bvd.engine.matcher import matcher
from bvd.engine.stats import stats_tracker

router = APIRouter()

_USD_PER_TOKEN = 0.000001  # approximate Claude Haiku input pricing — label only, not billing


def _script_to_dict(s: Script, *, include_code: bool = False) -> dict[str, Any]:
    d: dict[str, Any] = {
        "id": s.id,
        "name": s.name,
        "description": s.description,
        "tags": s.tags,
        "version": s.version,
        "author": s.author,
        "usage_count": s.usage_count,
        "avg_saving": s.avg_saving,
        "status": s.status,
        "created_at": s.created_at,
        "updated_at": s.updated_at,
    }
    if include_code:
        d["code"] = s.code
    return d


@router.get("/bibividi/api/stats")
async def get_stats() -> dict[str, Any]:
    """Aggregate token savings summary."""
    data = await stats_tracker.summary()
    if not data:
        data = {
            "total_calls": 0,
            "tokens_in": 0,
            "tokens_out": 0,
            "tokens_saved": 0,
            "saving_ratio": 0.0,
            "avg_latency_ms": 0.0,
        }
    data["estimated_usd_saved"] = round(data.get("tokens_saved", 0) * _USD_PER_TOKEN, 4)
    return data


@router.get("/bibividi/api/scripts")
async def get_scripts() -> list[dict[str, Any]]:
    """List active skills ordered by usage count."""
    scripts = await library.all()
    return [_script_to_dict(s) for s in scripts if s.status == "active"]


@router.get("/bibividi/api/pending")
async def get_pending() -> list[dict[str, Any]]:
    """List skills awaiting approval (include code for review)."""
    scripts = await library.list_pending()
    return [_script_to_dict(s, include_code=True) for s in scripts]


@router.post("/bibividi/api/pending/{skill_id}/approve")
async def approve_skill(skill_id: str) -> dict[str, Any]:
    """Approve a pending skill (activates it for execution)."""
    ok = await library.approve(skill_id)
    if not ok:
        raise HTTPException(status_code=404, detail=f"Pending skill '{skill_id}' not found")
    matcher.invalidate()
    return {"ok": True, "skill_id": skill_id}


_ROI_CALCULATOR_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BIBIVIDI — ROI Calculator</title>
<style>
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
  body {
    font-family: ui-monospace, monospace;
    background: #0f1318;
    color: #e2e8f0;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
  }
  .card {
    background: #1a1f2e;
    border: 1px solid rgba(45,212,160,0.25);
    border-radius: 12px;
    padding: 2.5rem;
    max-width: 560px;
    width: 100%;
    box-shadow: 0 0 40px rgba(45,212,160,0.06);
  }
  .logo {
    color: #2dd4a0; font-size: 0.75rem; letter-spacing: 2px;
    font-weight: bold; margin-bottom: 0.5rem;
  }
  h1 { font-size: 1.4rem; color: #fff; margin-bottom: 0.4rem; }
  .subtitle { color: #8b9eb5; font-size: 0.8rem; margin-bottom: 2rem; }
  .field { margin-bottom: 1.5rem; }
  label { display: flex; justify-content: space-between; align-items: baseline;
          font-size: 0.8rem; color: #94a3b8; margin-bottom: 0.5rem; }
  label span { color: #fff; font-size: 0.95rem; font-weight: bold; }
  input[type=range] {
    width: 100%; accent-color: #2dd4a0; cursor: pointer; height: 4px;
  }
  .model-row { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
  .model-btn {
    background: #0f1318; border: 1px solid #2a3444; color: #94a3b8;
    padding: 0.3rem 0.7rem; border-radius: 6px; font-family: inherit;
    font-size: 0.75rem; cursor: pointer; transition: all 0.15s;
  }
  .model-btn.active { border-color: #2dd4a0; color: #2dd4a0; background: rgba(45,212,160,0.08); }
  hr { border: none; border-top: 1px solid rgba(45,212,160,0.15); margin: 1.5rem 0; }
  .results { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
  .result-box {
    background: #0f1318; border: 1px solid #2a3444;
    border-radius: 8px; padding: 1rem; text-align: center;
  }
  .result-box .val { font-size: 1.5rem; font-weight: bold; color: #2dd4a0; }
  .result-box .lbl { font-size: 0.7rem; color: #8b9eb5; margin-top: 0.25rem; }
  .result-box.highlight { border-color: #2dd4a0; background: rgba(45,212,160,0.06); }
  .result-box.highlight .val { color: #fff; }
  .footer { margin-top: 1.5rem; font-size: 0.7rem; color: #4a5568; text-align: center; }
  .footer a { color: #2dd4a0; text-decoration: none; }
</style>
</head>
<body>
<div class="card">
  <div class="logo">BIBIVIDI</div>
  <h1>ROI Calculator</h1>
  <p class="subtitle">See your monthly savings before installing</p>

  <div class="field">
    <label>Monthly AI API spend <span id="spend-val">€500</span></label>
    <input type="range" id="spend" min="50" max="10000" step="50" value="500">
  </div>

  <div class="field">
    <label>Average context size <span id="tokens-val">10,000 tokens</span></label>
    <input type="range" id="tokens" min="500" max="200000" step="500" value="10000">
  </div>

  <div class="field">
    <label style="margin-bottom:0.5rem;">AI model <span id="model-price-label"></span></label>
    <div class="model-row" id="model-row"></div>
  </div>

  <hr>

  <div class="results">
    <div class="result-box highlight">
      <div class="val" id="r-gross">€0</div>
      <div class="lbl">Gross savings / month</div>
    </div>
    <div class="result-box">
      <div class="val" id="r-net">€0</div>
      <div class="lbl">Your net savings (after BIBIVIDI 20%)</div>
    </div>
    <div class="result-box">
      <div class="val" id="r-bibividi">€0</div>
      <div class="lbl">BIBIVIDI fee / month</div>
    </div>
    <div class="result-box">
      <div class="val" id="r-co2">0 kg</div>
      <div class="lbl">CO₂ avoided / month</div>
    </div>
  </div>

  <div class="footer">
    Assumes 75% avg token reduction · savings-share pricing at 20%<br>
    <a href="/bibividi/ui">Open dashboard</a> &nbsp;·&nbsp;
    Prices approximate — see <a href="https://openai.com/pricing" target="_blank">model pricing</a>
  </div>
</div>

<script>
const MODELS = [
  { id: "gpt-4o",         label: "GPT-4o",          price: 0.000005 },
  { id: "gpt-4o-mini",    label: "GPT-4o mini",      price: 0.00000015 },
  { id: "claude-haiku",   label: "Claude Haiku",     price: 0.00000025 },
  { id: "claude-sonnet",  label: "Claude Sonnet",    price: 0.000003 },
  { id: "claude-opus",    label: "Claude Opus",      price: 0.000015 },
  { id: "gemini-flash",   label: "Gemini Flash",     price: 0.0000001 },
];

let selectedModel = MODELS[2]; // Claude Haiku default

const modelRow = document.getElementById("model-row");
MODELS.forEach(m => {
  const btn = document.createElement("button");
  btn.className = "model-btn" + (m.id === selectedModel.id ? " active" : "");
  btn.textContent = m.label;
  btn.onclick = () => {
    selectedModel = m;
    document.querySelectorAll(".model-btn").forEach(b => b.classList.remove("active"));
    btn.classList.add("active");
    update();
  };
  modelRow.appendChild(btn);
});

function fmt(n) {
  if (n >= 1000) return "€" + (n/1000).toFixed(1) + "K";
  return "€" + n.toFixed(0);
}

function update() {
  const spend = +document.getElementById("spend").value;
  const tokens = +document.getElementById("tokens").value;

  document.getElementById("spend-val").textContent =
    spend >= 1000 ? "€" + (spend/1000).toFixed(1) + "K" : "€" + spend;
  document.getElementById("tokens-val").textContent =
    tokens >= 1000 ? (tokens/1000).toFixed(0) + "K tokens" : tokens + " tokens";

  // Gross savings: 75% reduction × monthly spend
  const reduction = 0.75;
  const gross = spend * reduction;
  const fee = gross * 0.20;
  const net = gross - fee;

  // Tokens saved estimate (monthly)
  const requestsPerMonth = Math.round(spend / (tokens * selectedModel.price));
  const tokensSavedPerMonth = requestsPerMonth * tokens * reduction;
  const co2Kg = (tokensSavedPerMonth * 0.003) / 1000;

  document.getElementById("r-gross").textContent = fmt(gross);
  document.getElementById("r-net").textContent = fmt(net);
  document.getElementById("r-bibividi").textContent = fmt(fee);
  document.getElementById("r-co2").textContent =
    co2Kg >= 1000 ? (co2Kg/1000).toFixed(1) + " t" : co2Kg.toFixed(1) + " kg";
}

document.getElementById("spend").addEventListener("input", update);
document.getElementById("tokens").addEventListener("input", update);
update();
</script>
</body>
</html>"""


@router.get("/bibividi/roi", response_class=Response)
async def roi_calculator() -> Response:
    """Interactive ROI calculator — shows estimated monthly savings before installing."""
    return Response(content=_ROI_CALCULATOR_HTML, media_type="text/html")


@router.get("/bibividi/badge.svg", response_class=Response)
async def badge_svg(token: str | None = Query(default=None)) -> Response:
    """
    Shareable monthly savings badge (SVG).
    Protected by BIBIVIDI_BADGE_TOKEN when set; public if not configured.
    """
    if settings.badge_token and token != settings.badge_token:
        raise HTTPException(status_code=403, detail="Invalid or missing badge token")

    data = await stats_tracker.monthly_summary()
    saved = data["tokens_saved"]
    co2 = data["co2_grams"]
    calls = data["calls"]

    # Format numbers for display
    if saved >= 1_000_000:
        saved_label = f"{saved / 1_000_000:.1f}M"
    elif saved >= 1_000:
        saved_label = f"{saved / 1_000:.0f}K"
    else:
        saved_label = str(saved)

    if co2 >= 1000:
        co2_label = f"{co2 / 1000:.1f}kg CO₂"
    else:
        co2_label = f"{co2:.0f}g CO₂"

    svg = f"""<svg xmlns="http://www.w3.org/2000/svg" width="220" height="56">
  <defs>
    <linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0%" stop-color="#1a1f2e"/>
      <stop offset="100%" stop-color="#0f1318"/>
    </linearGradient>
  </defs>
  <rect width="220" height="56" rx="6" fill="url(#bg)"/>
  <rect width="220" height="56" rx="6" fill="none"
        stroke="#2dd4a0" stroke-width="1" stroke-opacity="0.4"/>
  <!-- Logo dot -->
  <circle cx="14" cy="14" r="5" fill="#2dd4a0"/>
  <!-- Title -->
  <text x="24" y="18" font-family="monospace,ui-monospace" font-size="9"
        fill="#2dd4a0" font-weight="bold" letter-spacing="1">BIBIVIDI</text>
  <!-- Divider -->
  <line x1="8" y1="24" x2="212" y2="24" stroke="#2dd4a0" stroke-width="0.5" stroke-opacity="0.3"/>
  <!-- Tokens saved -->
  <text x="8" y="38" font-family="monospace,ui-monospace" font-size="11"
        fill="#ffffff" font-weight="bold">{saved_label} tokens saved</text>
  <!-- Sub-stats -->
  <text x="8" y="50" font-family="monospace,ui-monospace" font-size="9"
        fill="#8b9eb5">{co2_label} · {calls} calls · 30 days</text>
</svg>"""

    return Response(
        content=svg,
        media_type="image/svg+xml",
        headers={
            "Cache-Control": "max-age=3600, public",
            "X-Content-Type-Options": "nosniff",
        },
    )


@router.post("/bibividi/api/pending/{skill_id}/reject")
async def reject_skill(skill_id: str) -> dict[str, Any]:
    """Reject and delete a pending skill."""
    script = await library.get(skill_id)
    if not script or script.status != "pending":
        raise HTTPException(status_code=404, detail=f"Pending skill '{skill_id}' not found")
    await library.delete(skill_id)
    return {"ok": True, "skill_id": skill_id}


@router.get("/bibividi/api/billing/summary")
async def get_billing_summary(
    year: int = Query(default=None),
    month: int = Query(default=None),
) -> dict[str, Any]:
    """Monthly billing summary (savings, BIBIVIDI share, tier)."""
    from datetime import UTC, datetime

    from bvd.billing import monthly_billing

    now = datetime.now(UTC)
    y = year or now.year
    m = month or now.month
    result = await monthly_billing(y, m, tier=settings.billing_tier)
    return {
        "year": y,
        "month": m,
        "tier": settings.billing_tier,
        "savings_eur": result.savings_eur,
        "bibividi_share_eur": result.bibividi_share_eur,
        "net_eur": round(result.savings_eur - result.bibividi_share_eur, 4),
        "tokens_saved": result.tokens_saved,
        "executions": result.executions,
        "capped": result.capped,
    }


@router.get("/bibividi/api/marketplace/payouts")
async def get_marketplace_payouts(
    limit: int = Query(default=100, le=500),
) -> list[dict[str, Any]]:
    """List pending marketplace payout rows."""
    from bvd.marketplace.payouts import list_pending_payouts
    return await list_pending_payouts(limit=limit)


# ── First-run wizard endpoints ─────────────────────────────────────────────────

@router.get("/bibividi/api/config/status")
async def config_status() -> dict[str, Any]:
    """Return which credentials are already set (for wizard first-run detection)."""
    from bvd.license import load_license_key

    key = load_license_key()
    return {
        "license_key_set": bool(key),
        "generator_api_key_set": bool(settings.generator_api_key),
    }


@router.post("/bibividi/api/config/activate")
async def activate_license(body: dict[str, Any]) -> dict[str, Any]:
    """Validate and persist a license key (wizard step 1)."""
    import re

    from bvd.license import is_valid_format, save_license_key, validate_key_with_api

    key = str(body.get("key", "")).strip()
    if not is_valid_format(key):
        raise HTTPException(status_code=400, detail="Invalid key format — expected bvd-xxxxxxxx")

    valid = await validate_key_with_api(key, settings.billing_api_url)
    if not valid:
        raise HTTPException(status_code=401, detail="Key rejected by billing API")

    save_license_key(key)
    return {"ok": True}


@router.patch("/bibividi/api/config")
async def update_config(body: dict[str, Any]) -> dict[str, Any]:
    """Persist generator API key + provider to ~/.bibividi/.env (wizard step 2).

    The running proxy reads settings at startup from the env file, so a proxy
    restart is required for changes to take effect.  The caller is responsible
    for triggering the restart (Tauri restart_proxy command).
    """
    from pathlib import Path

    allowed_providers = {"anthropic", "openai", "gemini"}
    api_key = str(body.get("generator_api_key", "")).strip()
    provider = str(body.get("generator_provider", "anthropic")).strip().lower()

    if not api_key:
        raise HTTPException(status_code=400, detail="generator_api_key is required")
    if provider not in allowed_providers:
        raise HTTPException(status_code=400, detail=f"provider must be one of {allowed_providers}")

    env_path = Path.home() / ".bibividi" / ".env"
    env_path.parent.mkdir(parents=True, exist_ok=True)

    existing = env_path.read_text() if env_path.exists() else ""
    lines = [
        line for line in existing.splitlines()
        if not line.startswith("BIBIVIDI_GENERATOR_API_KEY=")
        and not line.startswith("BIBIVIDI_GENERATOR_PROVIDER=")
    ]
    lines.append(f"BIBIVIDI_GENERATOR_API_KEY={api_key}")
    lines.append(f"BIBIVIDI_GENERATOR_PROVIDER={provider}")
    env_path.write_text("\n".join(lines) + "\n")

    return {"ok": True, "restart_required": True}


_DEMO_PROMPT = """\
Extract and summarize the key legal clauses from this contract document.

SERVICE AGREEMENT

This Service Agreement is entered into as of January 1, 2026, between
Acme Corp ("Client") and Ropart Interactive ("Provider").

1. PAYMENT TERMS
   Client agrees to pay Provider a monthly fee of $5,000 USD. Invoices are due
   within 30 days. Late payments incur a 1.5% monthly fee.

2. TERMINATION
   Either party may terminate this Agreement with 30 days written notice. Provider
   may terminate immediately for non-payment exceeding 60 days.

3. CONFIDENTIALITY
   Both parties agree to keep proprietary information confidential for 3 years after
   termination. This NDA covers all technical specifications and business data.

4. LIABILITY
   Provider's liability is limited to fees paid in the prior 3 months. Neither party
   is liable for indirect or consequential damages.

5. INTELLECTUAL PROPERTY
   All work product created under this Agreement is owned by Client. Provider retains
   rights to pre-existing IP and general methodologies.

6. GOVERNING LAW
   This Agreement is governed by the laws of France. Disputes shall be resolved by
   arbitration in Paris.

Please identify all important clauses and explain their business implications in
detail, with references to case law where applicable, and provide a risk assessment
for each clause from both the client and provider perspective.
"""


@router.post("/bibividi/api/demo")
async def run_demo() -> dict[str, Any]:
    """Run the built-in pdf-clause-extractor demo and return token savings (wizard step 3)."""
    import tempfile
    from pathlib import Path
    from unittest.mock import patch

    from bvd.engine.library import Library
    from bvd.engine.runner import run_script

    pack = Path(__file__).parent.parent.parent / "scripts" / "pdf-clause-extractor"
    if not pack.exists():
        # Installed package: skills are in the package data directory
        pack = Path(__file__).parent.parent / "scripts" / "pdf-clause-extractor"
    if not pack.exists():
        raise HTTPException(status_code=503, detail="Demo skill pack not found")

    import json as _json

    skill_json = _json.loads((pack / "skill.json").read_text())
    skill_py = (pack / "skill.py").read_text()

    with tempfile.TemporaryDirectory() as tmp:
        with patch("bvd.config.settings.db_path", Path(tmp) / "demo.db"):
            lib = Library()
            await lib.init()
            script = await lib.import_bss(skill_json, skill_py)

        result = await run_script(script, _DEMO_PROMPT)

    tokens_before = max(1, len(_DEMO_PROMPT) // 4)
    tokens_after = max(1, len(result.output) // 4)
    saving_pct = round((1 - tokens_after / tokens_before) * 100, 1) if tokens_before > 0 else 0.0

    return {
        "tokens_before": tokens_before,
        "tokens_after": tokens_after,
        "saving_pct": saving_pct,
        "skill_id": "pdf-clause-extractor",
    }


@router.post("/bibividi/api/marketplace/payouts/{payout_id}/pay")
async def pay_payout(payout_id: int) -> dict[str, Any]:
    """Mark a marketplace payout as paid."""
    from bvd.marketplace.payouts import mark_payout_paid
    ok = await mark_payout_paid(payout_id)
    if not ok:
        raise HTTPException(status_code=404, detail=f"Payout {payout_id} not found or not pending")
    return {"ok": True, "payout_id": payout_id}
