import { useEffect, useMemo, useState } from "react";

import "./admin-assets.css";

interface AdminSession { csrf_token: string }
interface StorageState {
  capacity_notice_level: "normal" | "warning" | "critical";
  hard_limit_bytes: number;
  managed_content_bytes: number;
  storage_status: "active" | "full" | "unavailable";
}
interface AdminSticker {
  enabled: boolean;
  file_state: "committed";
  height: number;
  mime_type: "image/png" | "image/webp";
  order: number;
  original_byte_size: number;
  original_filename: string;
  part: number;
  resource_version: string;
  stable_id: string;
  thumbnail_byte_size: number;
  thumbnail_reference: { url: string };
  width: number;
}
interface AdminAssetsResponse {
  count: number;
  items: AdminSticker[];
  release_version: string | null;
  storage: StorageState;
}

function idempotencyKey() {
  return crypto.randomUUID().replaceAll("-", "") + crypto.randomUUID().replaceAll("-", "");
}

function bytesLabel(bytes: number) {
  return new Intl.NumberFormat("zh-CN", { maximumFractionDigits: 2, minimumFractionDigits: 2 }).format(bytes / (1024 ** 3));
}

async function loadJson<T>(url: string, init?: RequestInit) {
  const response = await fetch(url, { credentials: "same-origin", ...init });
  const body = response.headers.get("content-type")?.includes("application/json") ? await response.json() as T : undefined;
  return { body, response };
}

export function AdminAssetsPage() {
  const [session, setSession] = useState<AdminSession>();
  const [assets, setAssets] = useState<AdminAssetsResponse>();
  const [loadingFailed, setLoadingFailed] = useState(false);
  const [busy, setBusy] = useState(false);
  const [notice, setNotice] = useState("");
  const [file, setFile] = useState<File>();
  const [stableId, setStableId] = useState("STK1408");
  const [part, setPart] = useState(25);
  const [order, setOrder] = useState(184);
  const [enabled, setEnabled] = useState(true);
  const [orderDrafts, setOrderDrafts] = useState<Record<string, number>>({});

  async function load() {
    setLoadingFailed(false);
    try {
      const [sessionResult, assetsResult] = await Promise.all([
        loadJson<AdminSession>("/api/v1/admin-auth/session"),
        loadJson<AdminAssetsResponse>("/api/v1/admin/assets/static-stickers"),
      ]);
      if (!sessionResult.response.ok || !assetsResult.response.ok || !sessionResult.body || !assetsResult.body) throw new Error("load_failed");
      setSession(sessionResult.body);
      setAssets(assetsResult.body);
      setOrderDrafts(Object.fromEntries(assetsResult.body.items.map((item) => [item.stable_id, item.order])));
      const numericIds = assetsResult.body.items.map((item) => Number(item.stable_id.slice(3))).filter(Number.isFinite);
      setStableId(`STK${Math.max(1407, ...numericIds) + 1}`);
      setOrder(Math.max(183, ...assetsResult.body.items.filter((item) => item.part === 25).map((item) => item.order)) + 1);
      setNotice("");
    } catch {
      setLoadingFailed(true);
    }
  }

  useEffect(() => { void load(); }, []);

  const uploadBlocked = !assets || assets.storage.storage_status !== "active";
  const formValid = useMemo(() => Boolean(
    file && /^(image\/png|image\/webp)$/.test(file.type) && /^STK[0-9]{4,}$/.test(stableId)
      && Number.isSafeInteger(part) && part >= 1 && part <= 25 && Number.isSafeInteger(order) && order > 0,
  ), [file, order, part, stableId]);

  async function upload() {
    if (!file || !session || !formValid || uploadBlocked || busy) return;
    setBusy(true);
    setNotice("");
    try {
      const sha256 = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", await file.arrayBuffer())))
        .map((byte) => byte.toString(16).padStart(2, "0")).join("");
      const form = new FormData();
      form.append("stable_id", stableId);
      form.append("part", String(part));
      form.append("order", String(order));
      form.append("enabled", String(enabled));
      form.append("original_byte_size", String(file.size));
      form.append("original_sha256", sha256);
      form.append("sticker_file", file, file.name);
      const response = await fetch("/api/v1/admin/assets/static-stickers", {
        body: form,
        credentials: "same-origin",
        headers: { "Idempotency-Key": idempotencyKey(), "X-CSRF-Token": session.csrf_token },
        method: "POST",
      });
      if (!response.ok) {
        setNotice(response.status === 507 ? "存储容量已满或暂不可用，未写入任何文件。" : response.status === 409 ? "稳定 ID 或 part 顺序已存在。" : "文件格式、内容或字段校验未通过。");
        return;
      }
      setFile(undefined);
      await load();
      setNotice("贴纸已生成缩略图并发布新资源版本。");
    } catch {
      setNotice("上传未完成，未发布新资源版本。");
    } finally {
      setBusy(false);
    }
  }

  async function update(item: AdminSticker, change: { enabled?: boolean; order?: number }) {
    if (!session || busy) return;
    setBusy(true);
    setNotice("");
    try {
      const { response } = await loadJson(`/api/v1/admin/assets/static-stickers/${encodeURIComponent(item.stable_id)}`, {
        body: JSON.stringify(change),
        headers: { "Content-Type": "application/json", "X-CSRF-Token": session.csrf_token },
        method: "PATCH",
      });
      if (!response.ok) throw new Error("update_failed");
      await load();
      setNotice(change.enabled === false ? "贴纸已停用，新项目目录不再显示。" : change.enabled === true ? "贴纸已重新启用。" : "part 顺序已发布到新资源版本。");
    } catch {
      setNotice("素材状态未更新。");
    } finally {
      setBusy(false);
    }
  }

  return <div className="admin-assets-page">
    <header className="admin-product-header">
      <a href="/admin">DADA ADMIN</a>
      <nav aria-label="后台导航"><a href="/admin/users">用户</a><a href="/admin/models">模型</a><a aria-current="page" href="/admin/assets">素材</a><a href="/admin/audit">审计</a></nav>
    </header>
    <main>
      <header className="admin-assets-heading"><div><p>ASSET OPERATIONS</p><h1>普通贴纸</h1></div><strong>{assets?.release_version ?? "尚未发布"}</strong></header>
      {!assets && !loadingFailed ? <div aria-label="贴纸素材加载中" className="admin-assets-loading"><span /><span /><span /></div> : null}
      {loadingFailed ? <p className="admin-assets-alert" role="alert">素材状态暂时无法读取。<button onClick={() => void load()} type="button">重试</button></p> : null}
      {assets ? <>
        <div className="admin-assets-summary" aria-label="素材存储摘要">
          <span>后台贴纸<strong>{assets.count}</strong></span>
          <span>受管内容<strong>{bytesLabel(assets.storage.managed_content_bytes)} / {bytesLabel(assets.storage.hard_limit_bytes)} GB</strong></span>
          <span>存储状态<strong className={`is-${assets.storage.storage_status}`}>{assets.storage.storage_status}</strong></span>
        </div>
        <section className="admin-assets-upload" aria-labelledby="asset-upload-title">
          <header><h2 id="asset-upload-title">上传并发布</h2><span>PNG / WebP</span></header>
          <div className="admin-assets-form">
            <label>文件<input accept="image/png,image/webp" aria-label="贴纸文件" disabled={uploadBlocked || busy} key={file?.name ?? "empty"} onChange={(event) => setFile(event.target.files?.[0])} type="file" /></label>
            <label>稳定 ID<input aria-label="稳定 ID" disabled={uploadBlocked || busy} onChange={(event) => setStableId(event.target.value.toUpperCase())} value={stableId} /></label>
            <label>Part<input aria-label="Part" disabled={uploadBlocked || busy} max="25" min="1" onChange={(event) => setPart(Number(event.target.value))} type="number" value={part} /></label>
            <label>顺序<input aria-label="顺序" disabled={uploadBlocked || busy} min="1" onChange={(event) => setOrder(Number(event.target.value))} type="number" value={order} /></label>
            <label className="admin-assets-enabled"><input checked={enabled} disabled={uploadBlocked || busy} onChange={(event) => setEnabled(event.target.checked)} type="checkbox" />发布后启用</label>
            <button disabled={!formValid || uploadBlocked || busy} onClick={() => void upload()} type="button">{busy ? "处理中" : "上传并发布"}</button>
          </div>
          {uploadBlocked ? <p className="admin-assets-blocked" role="status">当前存储状态禁止新增原图和缩略图。</p> : null}
        </section>
        <section className="admin-assets-list" aria-labelledby="asset-list-title">
          <header><h2 id="asset-list-title">当前版本</h2><span>{assets.count} 项</span></header>
          {assets.items.length === 0 ? <p className="admin-assets-empty">当前没有后台上传的普通贴纸。</p> : <div className="admin-assets-table-wrap"><table>
            <thead><tr><th>预览</th><th>稳定 ID</th><th>Part / 顺序</th><th>原文件</th><th>尺寸</th><th>文件状态</th><th>发布状态</th><th>操作</th></tr></thead>
            <tbody>{assets.items.map((item) => <tr key={item.stable_id}>
              <td><img alt="" src={item.thumbnail_reference.url} /></td>
              <th scope="row"><strong>{item.stable_id}</strong><small>{item.resource_version}</small></th>
              <td><span>part{item.part}</span><input aria-label={`${item.stable_id} 顺序`} min="1" onChange={(event) => setOrderDrafts((current) => ({ ...current, [item.stable_id]: Number(event.target.value) }))} type="number" value={orderDrafts[item.stable_id] ?? item.order} /></td>
              <td><span>{item.original_filename}</span><small>{item.mime_type} · {item.original_byte_size.toLocaleString("zh-CN")} B</small></td>
              <td>{item.width} x {item.height}</td>
              <td>{item.file_state}</td>
              <td><span className={item.enabled ? "is-enabled" : "is-disabled"}>{item.enabled ? "已启用" : "已停用"}</span></td>
              <td><button disabled={busy || (orderDrafts[item.stable_id] ?? item.order) === item.order} onClick={() => void update(item, { order: orderDrafts[item.stable_id] ?? item.order })} type="button">更新顺序</button><button disabled={busy} onClick={() => void update(item, { enabled: !item.enabled })} type="button">{item.enabled ? "停用" : "启用"}</button></td>
            </tr>)}</tbody>
          </table></div>}
        </section>
        {notice ? <p className="admin-assets-notice" role="status">{notice}</p> : null}
      </> : null}
    </main>
  </div>;
}
