import { useMutation, useQuery } from "@tanstack/react-query";
import { Check, Copy, Download, FileText, GitBranch, Loader2, Search, ShieldCheck } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { normalizeApiError } from "../../api/client";
import { CopyableValue } from "../../components/common/CopyableValue";
import {
  createObjectSetExport,
  createPublicationPointExport,
  downloadRawObject,
  explainObjectValidation,
  getLatestRun,
  getObject,
  getObjectByUri,
  getObjectChain,
  getObjectParsed,
  getObjectValidation,
  listManifestFiles,
  listRevokedCertificates,
  type ChainEdgeRecord,
  type ObjectInstanceRecord,
  type ObjectValidationRecord,
  type ValidationExplainRecord
} from "../../api/queryService";

const tabs = ["Parsed", "Validation", "Chain", "Manifest files", "Revoked certs"] as const;
type ObjectTab = (typeof tabs)[number];

interface ObjectDetailPageProps {
  initialObjectInstanceId?: string | null;
}

export function ObjectDetailPage({ initialObjectInstanceId }: ObjectDetailPageProps) {
  const [selectedObjectInstanceId, setSelectedObjectInstanceId] = useState<string | null>(initialObjectInstanceId ?? null);
  const [activeTab, setActiveTab] = useState<ObjectTab>("Validation");
  const [copyState, setCopyState] = useState<"idle" | "copied" | "failed">("idle");
  const [searchUri, setSearchUri] = useState("");
  const latestRunQuery = useQuery({ queryKey: ["object-detail-latest-run"], queryFn: getLatestRun });
  const runId = latestRunQuery.data?.data.runId ?? "latest";
  useEffect(() => {
    if (initialObjectInstanceId && initialObjectInstanceId !== selectedObjectInstanceId) {
      setSelectedObjectInstanceId(initialObjectInstanceId);
      setActiveTab("Validation");
    }
  }, [initialObjectInstanceId, selectedObjectInstanceId]);

  const objectQuery = useQuery({
    enabled: Boolean(selectedObjectInstanceId && latestRunQuery.data?.data.runId),
    queryKey: ["object-detail-object", runId, selectedObjectInstanceId],
    queryFn: () => getObject(runId, selectedObjectInstanceId ?? ""),
    staleTime: 5 * 60 * 1000
  });
  const selectedObject = objectQuery.data?.data ?? null;
  const validationQuery = useQuery({
    enabled: Boolean(selectedObjectInstanceId && latestRunQuery.data?.data.runId),
    queryKey: ["object-detail-validation", runId, selectedObjectInstanceId],
    queryFn: () => getObjectValidation(runId, selectedObjectInstanceId ?? ""),
    staleTime: 5 * 60 * 1000
  });
  const parsedQuery = useQuery({
    enabled: activeTab === "Parsed" && Boolean(selectedObjectInstanceId && latestRunQuery.data?.data.runId),
    queryKey: ["object-detail-parsed", runId, selectedObjectInstanceId],
    queryFn: () => getObjectParsed(runId, selectedObjectInstanceId ?? ""),
    staleTime: 5 * 60 * 1000
  });
  const chainQuery = useQuery({
    enabled: activeTab === "Chain" && Boolean(selectedObjectInstanceId && latestRunQuery.data?.data.runId),
    queryKey: ["object-detail-chain", runId, selectedObjectInstanceId],
    queryFn: () => getObjectChain(runId, selectedObjectInstanceId ?? ""),
    staleTime: 5 * 60 * 1000
  });
  const manifestFilesQuery = useQuery({
    enabled: activeTab === "Manifest files" && Boolean(selectedObjectInstanceId && latestRunQuery.data?.data.runId),
    retry: false,
    queryKey: ["object-detail-manifest-files", runId, selectedObjectInstanceId],
    queryFn: () => listManifestFiles(runId, selectedObjectInstanceId ?? "", 50),
    staleTime: 5 * 60 * 1000
  });
  const revokedCertsQuery = useQuery({
    enabled: activeTab === "Revoked certs" && Boolean(selectedObjectInstanceId && latestRunQuery.data?.data.runId),
    retry: false,
    queryKey: ["object-detail-revoked-certs", runId, selectedObjectInstanceId],
    queryFn: () => listRevokedCertificates(runId, selectedObjectInstanceId ?? "", 50),
    staleTime: 5 * 60 * 1000
  });
  const explainMutation = useMutation({
    mutationFn: () => explainObjectValidation(runId, selectedObjectInstanceId ?? "")
  });
  const uriSearchMutation = useMutation({
    mutationFn: (uri: string) => getObjectByUri(runId, uri),
    onSuccess: (result) => {
      setSelectedObjectInstanceId(result.data.objectInstanceId);
      setActiveTab("Validation");
    }
  });
  const rawDownloadMutation = useMutation({
    mutationFn: () => downloadRawObject(runId, selectedObjectInstanceId ?? "")
  });
  const objectExportMutation = useMutation({
    mutationFn: () => createObjectSetExport(runId, selectedObjectInstanceId ? [selectedObjectInstanceId] : [])
  });
  const ppExportMutation = useMutation({
    mutationFn: () => {
      if (!selectedObject?.ppId) {
        throw new Error("No publication point selected");
      }
      return createPublicationPointExport(runId, selectedObject.ppId);
    }
  });

  useEffect(() => {
    if (copyState === "idle") {
      return;
    }
    const timeout = window.setTimeout(() => setCopyState("idle"), 1800);
    return () => window.clearTimeout(timeout);
  }, [copyState]);

  const copySelectedUri = async () => {
    if (!selectedObject?.uri) {
      return;
    }
    try {
      await navigator.clipboard.writeText(selectedObject.uri);
      setCopyState("copied");
    } catch {
      setCopyState("failed");
    }
  };

  const validation = validationQuery.data?.data ?? null;
  const apiError = latestRunQuery.error ?? objectQuery.error ?? validationQuery.error;

  return (
    <section className="page-stack object-page" aria-labelledby="object-detail-heading">
      {apiError ? <ErrorBanner error={apiError} /> : null}
      <div className="object-detail-only-layout">
          <aside className="object-detail-card object-detail-card-expanded" aria-label="Live object detail">
            <div className="object-detail-toolbar">
              <form
                className="search-box object-uri-search"
                onSubmit={(event) => {
                  event.preventDefault();
                  if (searchUri.trim()) {
                    uriSearchMutation.mutate(searchUri.trim());
                  }
                }}
              >
                <Search aria-hidden="true" size={17} />
                <span className="sr-only">Search exact object URI</span>
                <input
                  onChange={(event) => setSearchUri(event.target.value)}
                  placeholder="Exact URI lookup..."
                  value={searchUri}
                />
              </form>
              <button className="filter-pill" disabled={!selectedObject?.uri} type="button" onClick={() => selectedObject && setSearchUri(selectedObject.uri)}>Use selected URI</button>
              <strong>{selectedObject ? `${labelObjectType(selectedObject.objectType)} selected` : "No object selected"}</strong>
            </div>
            {!selectedObjectInstanceId ? <div className="empty-state">Search an exact URI or open an object from Repository Browser.</div> : null}
            {selectedObjectInstanceId && objectQuery.isFetching ? <LoadingLine label="Loading selected object..." /> : null}
            <div className="object-hero">
              <div>
                <p className="eyebrow">Object detail · live query service</p>
                <h2 id="object-detail-heading">{selectedObject ? labelObjectType(selectedObject.objectType) : "Object"} object</h2>
                {selectedObject ? (
                  <p><CopyableValue className="inline-copyable" label="selected object URI" value={selectedObject.uri} /></p>
                ) : (
                  <p>Select an object from the publication point list.</p>
                )}
              </div>
              <div className="object-actions">
                <StatusPill state={validation?.finalStatus ?? selectedObject?.result ?? "loading"} />
                <button className="ghost-button" disabled={!selectedObject?.uri} onClick={copySelectedUri} type="button">
                  <Copy size={16} /> {copyState === "copied" ? "Copied" : copyState === "failed" ? "Copy failed" : "Copy URI"}
                </button>
                <button className="ghost-button" disabled={!selectedObjectInstanceId} onClick={() => objectExportMutation.mutate()} type="button">
                  <FileText size={16} /> Export object
                </button>
                <button className="primary-button" disabled={!selectedObjectInstanceId} onClick={() => rawDownloadMutation.mutate()} type="button">
                  <Download size={16} /> Download raw
                </button>
              </div>
            </div>

            {selectedObject ? <ObjectMeta object={selectedObject} validation={validation} /> : <div className="empty-state">No object selected.</div>}
            <WorkflowStatus
              objectExport={objectExportMutation.data?.data ?? null}
              objectExportError={objectExportMutation.error}
              ppExport={ppExportMutation.data?.data ?? null}
              ppExportError={ppExportMutation.error}
              rawError={rawDownloadMutation.error}
              rawPending={rawDownloadMutation.isPending}
              searchError={uriSearchMutation.error}
              searchPending={uriSearchMutation.isPending}
            />
            <div className="object-actions inline-actions">
              <button className="ghost-button" disabled={!selectedObject?.ppId} onClick={() => ppExportMutation.mutate()} type="button">
                <FileText size={16} /> Export selected PP
              </button>
            </div>

            <div className="tabs" role="tablist" aria-label="Object detail tabs" onKeyDown={(event) => {
              if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") {
                return;
              }
              event.preventDefault();
              const currentIndex = tabs.indexOf(activeTab);
              const nextIndex = event.key === "ArrowRight"
                ? (currentIndex + 1) % tabs.length
                : (currentIndex - 1 + tabs.length) % tabs.length;
              setActiveTab(tabs[nextIndex]);
            }}>
              {tabs.map((tab) => (
                <button
                  aria-controls={`object-tabpanel-${tabId(tab)}`}
                  aria-selected={activeTab === tab}
                  className={activeTab === tab ? "active" : ""}
                  id={`object-tab-${tabId(tab)}`}
                  key={tab}
                  onClick={() => setActiveTab(tab)}
                  role="tab"
                  tabIndex={activeTab === tab ? 0 : -1}
                  type="button"
                >
                  {tab}
                </button>
              ))}
            </div>

            <div
              aria-labelledby={`object-tab-${tabId(activeTab)}`}
              className="object-content-grid"
              id={`object-tabpanel-${tabId(activeTab)}`}
              role="tabpanel"
            >
              {activeTab === "Parsed" ? <ParsedPanel parsed={parsedQuery.data?.data ?? null} isFetching={parsedQuery.isFetching} error={parsedQuery.error} /> : null}
              {activeTab === "Validation" ? (
                <ValidationPanel validation={validation} isFetching={validationQuery.isFetching} explain={explainMutation.data?.data ?? null} explainPending={explainMutation.isPending} onExplain={() => explainMutation.mutate()} explainError={explainMutation.error} />
              ) : null}
              {activeTab === "Chain" ? <ChainPanel edges={chainQuery.data?.data ?? []} isFetching={chainQuery.isFetching} error={chainQuery.error} /> : null}
              {activeTab === "Manifest files" ? <ArrayPanel title="Manifest files" rows={manifestFilesQuery.data?.data ?? []} isFetching={manifestFilesQuery.isFetching} error={manifestFilesQuery.error} /> : null}
              {activeTab === "Revoked certs" ? <ArrayPanel title="Revoked certificates" rows={revokedCertsQuery.data?.data ?? []} isFetching={revokedCertsQuery.isFetching} error={revokedCertsQuery.error} /> : null}
            </div>
          </aside>
      </div>
    </section>
  );
}

function ObjectMeta({ object, validation }: { object: ObjectInstanceRecord; validation: ObjectValidationRecord | null }) {
  return (
    <div className="object-meta-grid">
      <Meta label="SHA256" value={object.sha256} copyable />
      <Meta label="Source" value={object.sourceSection} />
      <Meta label="Repository" value={object.repoId} copyable />
      <Meta label="Publication Point" value={object.ppId} copyable />
      <Meta label="Audit Result" value={validation?.auditResult ?? object.result} />
      <Meta label="Authoritative" value={validation ? String(validation.authoritative) : "loading"} />
    </div>
  );
}

function WorkflowStatus({
  objectExport,
  objectExportError,
  ppExport,
  ppExportError,
  rawError,
  rawPending,
  searchError,
  searchPending
}: {
  objectExport: { jobId?: string; status?: string } | null;
  objectExportError: unknown;
  ppExport: { jobId?: string; status?: string } | null;
  ppExportError: unknown;
  rawError: unknown;
  rawPending: boolean;
  searchError: unknown;
  searchPending: boolean;
}) {
  const lines = [
    searchPending ? "Searching object URI..." : null,
    searchError ? `URI search failed: ${normalizeApiError(searchError).message}` : null,
    rawPending ? "Downloading raw object..." : null,
    rawError ? `Raw download failed: ${normalizeApiError(rawError).message}` : null,
    objectExport ? `Object export job ${objectExport.jobId ?? ""} ${objectExport.status ?? ""}`.trim() : null,
    objectExportError ? `Object export failed: ${normalizeApiError(objectExportError).message}` : null,
    ppExport ? `PP export job ${ppExport.jobId ?? ""} ${ppExport.status ?? ""}`.trim() : null,
    ppExportError ? `PP export failed: ${normalizeApiError(ppExportError).message}` : null
  ].filter((line): line is string => Boolean(line));
  if (lines.length === 0) {
    return null;
  }
  return (
    <section className="panel workflow-status">
      <PanelHeading eyebrow="Workflow" title="Search / raw / export status" />
      <ul className="workflow-status-list">
        {lines.map((line) => (
          <li key={line}>{line}</li>
        ))}
      </ul>
    </section>
  );
}

function ParsedPanel({ parsed, isFetching, error }: { parsed: unknown; isFetching: boolean; error: unknown }) {
  if (isFetching) {
    return <LoadingPanel title="Parsed projection" label="Loading parsed projection..." />;
  }
  if (error) {
    return <NoticePanel title="Parsed projection" message={normalizeApiError(error).message} />;
  }
  if (!parsed) {
    return <NoticePanel title="Parsed projection" message="Projection unavailable. The local query service was started without repo-bytes.db, so this object cannot be decoded on demand." />;
  }
  return (
    <section className="panel parsed-panel">
      <PanelHeading eyebrow="Parsed projection" title="Projection JSON" icon={<FileText className="panel-icon" size={22} />} />
      <JsonPreview value={parsed} />
    </section>
  );
}

function ValidationPanel({ validation, isFetching, explain, explainPending, onExplain, explainError }: {
  validation: ObjectValidationRecord | null;
  isFetching: boolean;
  explain: ValidationExplainRecord | null;
  explainPending: boolean;
  onExplain: () => void;
  explainError: unknown;
}) {
  if (isFetching) {
    return <LoadingPanel title="Validation" label="Loading validation summary..." />;
  }
  return (
    <section className="panel validation-panel">
      <PanelHeading eyebrow="Validation" title="File and chain checks" icon={<ShieldCheck className="panel-icon success" size={22} />} />
      <div className="check-list">
        <ValidationCheck label="Final status" status={validation?.finalStatus ?? "unknown"} note={validation?.detailSummary ?? "Derived from run audit state."} />
        <ValidationCheck label="File validation" status={validation?.fileValidation.status ?? "unknown"} note={issuesText(validation?.fileValidation.issues) ?? validation?.fileValidation.detailSummary ?? "No file issues recorded."} />
        <ValidationCheck label="Chain validation" status={validation?.chainValidation.status ?? "unknown"} note={issuesText(validation?.chainValidation.issues) ?? validation?.chainValidation.note ?? "No chain issues recorded."} />
      </div>
      <div className="explain-box">
        <button className="ghost-button" disabled={explainPending} onClick={onExplain} type="button">
          {explainPending ? <Loader2 size={16} /> : <ShieldCheck size={16} />}
          Explain validation
        </button>
        <span>Explain is an audit projection, not full authoritative revalidation.</span>
      </div>
      {explainError ? <div className="empty-state">Explain failed: {normalizeApiError(explainError).message}</div> : null}
      {explain ? (
        <div className="detail-list explain-result">
          <Meta label="Mode" value={explain.explainMode} />
          <Meta label="Authoritative" value={String(explain.authoritative)} />
          <Meta label="Generated" value={explain.generatedAt} />
          <Meta label="Edges" value={String(explain.chainEdges.length)} />
        </div>
      ) : null}
    </section>
  );
}

function ChainPanel({ edges, isFetching, error }: { edges: ChainEdgeRecord[]; isFetching: boolean; error: unknown }) {
  if (isFetching) {
    return <LoadingPanel title="Certificate chain" label="Loading chain edges..." />;
  }
  if (error) {
    return <NoticePanel title="Certificate chain" message={normalizeApiError(error).message} />;
  }
  return (
    <section className="panel chain-panel">
      <PanelHeading eyebrow="Certificate chain" title="Audit edge graph" icon={<GitBranch className="panel-icon" size={22} />} />
      {edges.length === 0 ? <div className="empty-state">No chain edges recorded for this object.</div> : null}
      <div className="chain-flow live-chain-flow">
        {edges.map((edge) => (
          <div className="chain-node" key={`${edge.relation}-${edge.toUri}`}>
            <strong>{edge.relation}</strong>
            <span>{edge.status}</span>
            <code><CopyableValue label="chain edge URI" value={edge.toUri} /></code>
          </div>
        ))}
      </div>
    </section>
  );
}

function ArrayPanel({ title, rows, isFetching, error }: { title: string; rows: unknown[]; isFetching: boolean; error: unknown }) {
  if (isFetching) {
    return <LoadingPanel title={title} label={`Loading ${title.toLowerCase()}...`} />;
  }
  if (error) {
    return <NoticePanel title={title} message={normalizeApiError(error).message} />;
  }
  return (
    <section className="panel table-panel">
      <PanelHeading eyebrow={title} title="Paged projection list" />
      {rows.length === 0 ? <div className="empty-state">No rows returned for this object.</div> : null}
      {rows.length > 0 ? <JsonPreview value={rows} /> : null}
    </section>
  );
}

function LoadingPanel({ title, label }: { title: string; label: string }) {
  return (
    <section className="panel">
      <PanelHeading eyebrow={title} title={title} />
      <LoadingLine label={label} />
    </section>
  );
}

function NoticePanel({ title, message }: { title: string; message: string }) {
  return (
    <section className="panel">
      <PanelHeading eyebrow={title} title={title} />
      <div className="empty-state">{message}</div>
    </section>
  );
}

function PanelHeading({ eyebrow, title, icon }: { eyebrow: string; title: string; icon?: React.ReactNode }) {
  return (
    <div className="panel-heading">
      <div>
        <p className="eyebrow">{eyebrow}</p>
        <h3>{title}</h3>
      </div>
      {icon}
    </div>
  );
}

function ValidationCheck({ label, status, note }: { label: string; status: string; note: string }) {
  const ok = status === "valid" || status === "ok";
  return (
    <article className={`check-item ${ok ? "" : "warning-check"}`}>
      <Check size={16} />
      <div>
        <strong>{label}: {status}</strong>
        <p>{note}</p>
      </div>
    </article>
  );
}

function JsonPreview({ value }: { value: unknown }) {
  const text = useMemo(() => JSON.stringify(value, null, 2), [value]);
  return <pre className="json-preview">{text}</pre>;
}

function ErrorBanner({ error }: { error: unknown }) {
  return (
    <div className="alert-banner" role="status">
      <strong>Object API unavailable</strong>
      <span>{normalizeApiError(error).message}</span>
    </div>
  );
}

function StatusPill({ state }: { state: string }) {
  const normalized = state.toLowerCase();
  const className = normalized.includes("fail") || normalized.includes("reject") || normalized.includes("error") || normalized.includes("invalid")
    ? "warning"
    : normalized.includes("cache") || normalized.includes("fallback")
      ? "fallback"
      : "healthy";
  return <span className={`status-badge ${className}`}>{state}</span>;
}

function LoadingLine({ label }: { label: string }) {
  return (
    <div className="loading-line">
      <Loader2 size={16} />
      {label}
    </div>
  );
}

function Meta({ copyable, label, value }: { copyable?: boolean; label: string; value: string }) {
  return (
    <div className="meta-item">
      <span>{label}</span>
      <strong>{copyable ? <CopyableValue label={label} value={value} /> : value}</strong>
    </div>
  );
}

function labelObjectType(type: string) {
  const labels: Record<string, string> = {
    certificate: "CER",
    crl: "CRL",
    manifest: "MFT",
    roa: "ROA",
    aspa: "ASPA"
  };
  return labels[type] ?? type.toUpperCase();
}

function tabId(tab: ObjectTab) {
  return tab.toLowerCase().replaceAll(" ", "-");
}

function issuesText(issues: { summary?: string }[] | undefined) {
  if (!issues || issues.length === 0) {
    return null;
  }
  return issues.map((issue) => issue.summary).filter(Boolean).join("; ");
}
