from __future__ import annotations

import html
import json

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

from ..layout.page import page


def register(app: FastAPI) -> None:
    @app.get("/ui/serving")
    async def ui_serving() -> HTMLResponse:
        body = """
<h1>Serving</h1>
<div class="card">
  <div class="row">
    <button class="btn" id="refresh">Refresh</button>
    <a class="btn" href="/ui/serving/new" style="display:inline-block">New Model</a>
  </div>
  <div style="height:10px"></div>
  <div id="out" class="muted">Loading...</div>
</div>
""".strip()
        script = """
document.getElementById("nav-ray-dashboard").href = curOriginWithPort(8265);
const out = document.getElementById("out");

function pill(state) {
  const s = String(state || "");
  if (s === "RUNNING") return `<span class="pill ok">${s}</span>`;
  if (s === "FAILED") return `<span class="pill bad">${s}</span>`;
  return `<span class="pill">${s}</span>`;
}

async function refresh() {
  out.textContent = "Loading...";
  try {
    const lim = 50;
    const off = Number(localStorage.getItem("mvp_serving_offset") || "0") || 0;
    const resp = await apiJson("/api/v2/serve/models?limit=" + lim + "&offset=" + off + "&include_deleted=0");
    const items = resp.items || [];
    const hasMore = !!resp.has_more;
    const pageNo = Math.floor(off / lim) + 1;
    const prevDisabled = off <= 0;
    const nextDisabled = !hasMore;

    const baseHint = curOriginWithPort(8000) + "/serve/<model_key>/v1";
    function row(m) {
      const endpoint = (m.endpoint && m.endpoint.openai_base_url) || (curOriginWithPort(8000) + "/serve/" + encodeURIComponent(m.model_key) + "/v1");
      return `<tr>
        <td><a href="/ui/serving/${m.model_key}">${m.model_key}</a></td>
        <td><code>${m.model_id}</code></td>
        <td>${pill(m.state)}</td>
        <td>${m.num_replicas} × ${m.gpus_per_replica} GPU</td>
        <td><code>${endpoint}</code></td>
        <td>${m.updated_at || ""}</td>
      </tr>`;
    }
    const rows = items.map(row).join("");

    out.innerHTML = `
      <div class="row" style="justify-content: space-between; margin-bottom: 8px;">
        <div class="muted">Per-model OpenAI base: <code>${baseHint}</code></div>
        <div class="row">
          <span class="muted">Page ${pageNo}</span>
          <button class="btn" id="prev" ${prevDisabled ? "disabled" : ""}>Prev</button>
          <button class="btn" id="next" ${nextDisabled ? "disabled" : ""}>Next</button>
        </div>
      </div>
      <table>
        <thead><tr><th>Model Key</th><th>Model ID</th><th>State</th><th>Resources</th><th>Endpoint</th><th>Updated</th></tr></thead>
        <tbody>${rows || "<tr><td colspan=6 class=muted>(none)</td></tr>"}</tbody>
      </table>
    `;

    const prevBtn = document.getElementById("prev");
    const nextBtn = document.getElementById("next");
    if (prevBtn) prevBtn.onclick = () => { localStorage.setItem("mvp_serving_offset", String(Math.max(0, off - lim))); refresh(); };
    if (nextBtn) nextBtn.onclick = () => { localStorage.setItem("mvp_serving_offset", String(off + lim)); refresh(); };
  } catch (e) {
    let text = "Error: " + (e.status || "") + "\\n" + (e.body || String(e));
    if (e.body && String(e.body).includes("serving is not enabled")) {
      text = "Serving is not enabled in server config.\\nAsk admin to enable `serving:` in dev.yaml.";
    }
    out.textContent = text;
  }
}

document.getElementById("refresh").onclick = refresh;
refresh();
""".strip()
        return HTMLResponse(content=page("Serving", "serving", body, script))

    @app.get("/ui/serving/new")
    async def ui_serving_new() -> HTMLResponse:
        example = """# ServingSpec (YAML)
# 说明：
# - model_id: 这里是 suffix（平台会自动加前缀：<user_id>-<YYYYMMDDHHMM>-<suffix>）
# - model_source: 本地模型路径（支持 $HOME 宏；推荐使用 $HOME/common/hf 指向共享 HF cache）
#
# 常用路径：
# - $HOME/common/hf -> /private/hf
# - $HOME -> /private/users/<user_id>
#
model_id: qwen-0.5b
model_source: $HOME/common/hf/hub/models--Qwen--Qwen2.5-0.5B-Instruct/snapshots/<SNAPSHOT_HASH>
num_replicas: 1
gpus_per_replica: 1

# engine_kwargs:  # 可选：透传给 vLLM
#   gpu_memory_utilization: 0.4
""".strip()
        body = f"""
<h1>New Model</h1>
<div class="card">
  <div class="muted">Paste ServingSpec YAML and submit to <code>/api/v2/serve/models</code>.</div>
  <div style="height:10px"></div>
  <textarea id="yaml" rows="14">{html.escape(example)}</textarea>
  <div style="height:10px"></div>
  <div class="row">
    <button class="btn" id="submit">Submit</button>
    <a class="btn" href="/ui/serving" style="display:inline-block">Back</a>
  </div>
  <div style="height:10px"></div>
  <pre id="out" class="muted"></pre>
</div>
""".strip()
        script = """
document.getElementById("nav-ray-dashboard").href = curOriginWithPort(8265);
const out = document.getElementById("out");
document.getElementById("submit").onclick = async () => {
  out.textContent = "Submitting...";
  const yaml = document.getElementById("yaml").value || "";
  try {
    const resp = await apiJson("/api/v2/serve/models", { method: "POST", headers: { "Content-Type": "application/yaml" }, body: yaml });
    out.textContent = "Created: " + resp.model_key + "\\nState: " + resp.state;
    if (resp.model_key) window.location.href = "/ui/serving/" + encodeURIComponent(resp.model_key);
  } catch (e) {
    out.textContent = "Error: " + (e.status || "") + "\\n" + (e.body || String(e));
  }
};
""".strip()
        return HTMLResponse(content=page("New Model", "serving", body, script))

    @app.get("/ui/serving/{model_key}")
    async def ui_serving_detail(model_key: str) -> HTMLResponse:
        body = f"""
<h1>Model</h1>
<div class="card">
  <div class="row" style="justify-content: space-between;">
    <div class="muted">model_key: <code>{html.escape(model_key)}</code></div>
    <div class="row">
      <a class="btn" href="/ui/serving" style="display:inline-block">Back</a>
      <a class="btn" id="openai-models" target="_blank" rel="noopener" href="#">OpenAI /v1/models</a>
    </div>
  </div>
  <div style="height:10px"></div>
  <div class="row">
    <label class="muted" style="min-width:120px">Scale replicas</label>
    <input id="replicas" type="number" min="1" step="1" value="1" style="max-width: 180px" />
    <button class="btn" id="scale">Apply</button>
    <button class="btn danger" id="delete">Delete</button>
  </div>
  <div style="height:10px"></div>
  <div id="meta" class="muted">Loading...</div>
  <div style="height:12px"></div>
  <h3 style="margin-top:0">Resolved Spec (YAML)</h3>
  <pre id="spec" class="muted">(loading)</pre>
  <div style="height:12px"></div>
  <h3 style="margin-top:0">Events</h3>
  <div id="events" class="muted">(loading)</div>
  <div style="height:12px"></div>
  <h3 style="margin-top:0">OpenAI Example</h3>
  <pre id="example" class="muted">(loading)</pre>
</div>
""".strip()
        script = f"""
document.getElementById("nav-ray-dashboard").href = curOriginWithPort(8265);
const modelKey = {json.dumps(model_key)};
document.getElementById("openai-models").href = curOriginWithPort(8000) + "/serve/" + encodeURIComponent(modelKey) + "/v1/models";
const meta = document.getElementById("meta");
const spec = document.getElementById("spec");
const eventsEl = document.getElementById("events");
const example = document.getElementById("example");
const replicas = document.getElementById("replicas");

function pill(state) {{
  const s = String(state || "");
  if (s === "RUNNING") return `<span class="pill ok">${{s}}</span>`;
  if (s === "FAILED") return `<span class="pill bad">${{s}}</span>`;
  return `<span class="pill">${{s}}</span>`;
}}

function renderEvents(events) {{
  if (!events || !events.length) return "<div class=muted>(none)</div>";
  const rows = events.map(e => {{
    const payload = (e.payload_json || "");
    const short = String(payload).length > 240 ? String(payload).slice(0, 240) + "..." : String(payload);
    return `<tr><td>${{e.created_at || ""}}</td><td><code>${{e.event_type}}</code></td><td><pre class=muted style=\\"margin:0\\">${{short}}</pre></td></tr>`;
  }}).join("");
  return `<table><thead><tr><th>Time</th><th>Type</th><th>Payload</th></tr></thead><tbody>${{rows}}</tbody></table>`;
}}

async function refresh() {{
  meta.textContent = "Loading...";
  spec.textContent = "(loading)";
  eventsEl.textContent = "(loading)";
  example.textContent = "(loading)";
  try {{
    const obj = await apiJson("/api/v2/serve/models/" + encodeURIComponent(modelKey));
    const m = obj.model || {{}};
    replicas.value = String(m.num_replicas || 1);
    const endpoint = (m.endpoint && m.endpoint.openai_base_url) || (curOriginWithPort(8000) + "/serve/" + encodeURIComponent(modelKey) + "/v1");
    meta.innerHTML = `
      <div class=row>
        <div>state: ${{pill(m.state)}}</div>
        <div class=muted>model_id: <code>${{m.model_id || ""}}</code></div>
        <div class=muted>source: <code>${{m.model_source || ""}}</code></div>
      </div>
      <div class=muted>endpoint: <code>${{endpoint}}</code></div>
    `;
    spec.textContent = obj.resolved_spec_yaml || "";
    eventsEl.innerHTML = renderEvents(obj.events || []);
    const base = endpoint;
    const mid = m.model_id || "";
    example.textContent = `curl -sS -H 'Content-Type: application/json' \\\\\\n  -X POST ${{base}}/chat/completions \\\\\\n  --data-binary '{{\\"model\\":\\"${{mid}}\\",\\"messages\\":[{{\\"role\\":\\"user\\",\\"content\\":\\"hello\\"}}],\\"max_tokens\\":16,\\"stream\\":false}}' | python3 -m json.tool`;
  }} catch (e) {{
    meta.textContent = "Error: " + (e.status || "") + "\\n" + (e.body || String(e));
    spec.textContent = "";
    eventsEl.textContent = "";
    example.textContent = "";
  }}
}}

document.getElementById("scale").onclick = async () => {{
  const n = Number(replicas.value || "1");
  if (!Number.isFinite(n) || n < 1) return;
  try {{
    await apiJson("/api/v2/serve/models/" + encodeURIComponent(modelKey), {{ method: "PATCH", headers: {{ "Content-Type": "application/json" }}, body: JSON.stringify({{ num_replicas: n }}) }});
    await refresh();
  }} catch (e) {{
    meta.textContent = "Error: " + (e.status || "") + "\\n" + (e.body || String(e));
  }}
}};

document.getElementById("delete").onclick = async () => {{
  if (!confirm("Delete this model?")) return;
  try {{
    await apiJson("/api/v2/serve/models/" + encodeURIComponent(modelKey), {{ method: "DELETE" }});
    await refresh();
  }} catch (e) {{
    meta.textContent = "Error: " + (e.status || "") + "\\n" + (e.body || String(e));
  }}
}};

refresh();
""".strip()
        return HTMLResponse(content=page("Model", "serving", body, script))

