// LLM configurations panel.
//
// One unified surface for everything LLM-config-related: server-persisted
// configurations, the per-operator "active" choice (which one the assistant
// uses on the next message), and 30-day spend per row. The "active" toggle
// writes to the same localStorage key @crema/llm-providers-ui reads via
// loadSettings/saveSettings, so the existing assistant code picks it up
// without any plumbing changes.

import { useCallback, useEffect, useMemo, useState } from "react"
import { Pencil, Plus, Sparkles, Star, Trash2, Upload } from "lucide-react"
import { useArcadiaClient } from "@crema/arcadia-client"
import {
  loadSettings as loadActiveSettings,
  saveSettings as saveActiveSettings,
  type LLMProvidersSettings,
} from "@crema/llm-providers-ui"

import { Button } from "~/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "~/components/ui/card"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "~/components/ui/dialog"
import { Input } from "~/components/ui/input"
import { Label } from "~/components/ui/label"
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "~/components/ui/select"
import { Switch } from "~/components/ui/switch"
import {
  createConfiguration,
  deleteConfiguration,
  findSpend,
  formatCost,
  getCatalog,
  getUsageByModel,
  getUsageSummary,
  listConfigurations,
  updateConfiguration,
  type CatalogEntry,
  type LlmConfiguration,
  type LlmConfigurationInput,
  type LlmProvider,
  type LlmUsageSummary,
  type UsageByModelRow,
} from "~/lib/arcadia/llm-configs"
import { listSecrets, type Secret } from "~/lib/arcadia/secrets"

const PROVIDERS: LlmProvider[] = ["openai", "anthropic", "deepseek", "qwen", "lmstudio"]
const LOCAL_SETTINGS_KEY = "crema.llm-providers.settings"

// Curated picks for the "Seed catalog" empty-state action — operators get
// a sensible starting set instead of a blank panel and 19 manual creates.
const SEED_PICKS: Array<{ name: string; provider: LlmProvider; model: string }> = [
  { name: "GPT-4o mini (cheap default)", provider: "openai", model: "gpt-4o-mini" },
  { name: "GPT-4o", provider: "openai", model: "gpt-4o" },
  { name: "Claude Sonnet 4.6", provider: "anthropic", model: "claude-sonnet-4-6" },
  { name: "Claude Haiku 4.5", provider: "anthropic", model: "claude-haiku-4-5" },
  { name: "DeepSeek V4 Flash", provider: "deepseek", model: "deepseek-v4-flash" },
  { name: "LM Studio (local)", provider: "lmstudio", model: "local-model" },
]

export function LlmConfigurationsPanel() {
  const arcadia = useArcadiaClient()
  const [configs, setConfigs] = useState<LlmConfiguration[]>([])
  const [catalog, setCatalog] = useState<CatalogEntry[]>([])
  const [usage, setUsage] = useState<LlmUsageSummary | null>(null)
  const [usageByModel, setUsageByModel] = useState<UsageByModelRow[]>([])
  const [secrets, setSecrets] = useState<Secret[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [active, setActive] = useState<LLMProvidersSettings | null>(null)
  const [editing, setEditing] = useState<LlmConfiguration | "new" | null>(null)

  const refresh = useCallback(async () => {
    setError(null)
    try {
      const [list, cat, sum, byModel, secs] = await Promise.all([
        listConfigurations(arcadia),
        getCatalog(arcadia).catch(() => [] as CatalogEntry[]),
        getUsageSummary(arcadia, { days: 30 }).catch(() => null),
        getUsageByModel(arcadia, { days: 30 }).catch(() => [] as UsageByModelRow[]),
        listSecrets(arcadia).catch(() => [] as Secret[]),
      ])
      setConfigs(list)
      setCatalog(cat)
      setUsage(sum)
      setUsageByModel(byModel)
      setSecrets(secs)
    } catch (e) {
      setError(e instanceof Error ? e.message : "Failed to load configurations.")
    } finally {
      setLoading(false)
    }
  }, [arcadia])

  useEffect(() => {
    void refresh()
    if (typeof window !== "undefined") setActive(loadActiveSettings())
  }, [refresh])

  const isActive = useCallback(
    (c: LlmConfiguration) =>
      !!active &&
      active.providerId === c.provider &&
      active.model === c.model &&
      (active.secretName || "") === (c.secret_name || ""),
    [active],
  )

  const onMakeActive = (c: LlmConfiguration) => {
    const current = loadActiveSettings()
    saveActiveSettings({
      ...current,
      providerId: c.provider as LLMProvidersSettings["providerId"],
      model: c.model,
      baseURL: c.base_url || undefined,
      secretName: c.secret_name || undefined,
    })
    setActive(loadActiveSettings())
  }

  const onToggleEnabled = async (c: LlmConfiguration) => {
    setError(null)
    try {
      await updateConfiguration(arcadia, c.id, { enabled: !c.enabled })
      await refresh()
    } catch (e) {
      setError(e instanceof Error ? e.message : "Update failed.")
    }
  }

  const onDelete = async (c: LlmConfiguration) => {
    setError(null)
    if (!window.confirm(`Delete "${c.name}"? Historical usage rows are preserved.`)) return
    try {
      await deleteConfiguration(arcadia, c.id)
      await refresh()
    } catch (e) {
      setError(e instanceof Error ? e.message : "Delete failed.")
    }
  }

  const onSave = async (input: LlmConfigurationInput, existing: LlmConfiguration | null) => {
    setError(null)
    try {
      if (existing) {
        await updateConfiguration(arcadia, existing.id, input)
      } else {
        await createConfiguration(arcadia, input)
      }
      setEditing(null)
      await refresh()
    } catch (e) {
      throw e instanceof Error ? e : new Error(String(e))
    }
  }

  const onSeed = async () => {
    setError(null)
    try {
      // Seed sequentially to surface conflicts cleanly.
      for (const pick of SEED_PICKS) {
        try {
          await createConfiguration(arcadia, pick)
        } catch {
          // skip dupes — they're benign on a re-seed
        }
      }
      await refresh()
    } catch (e) {
      setError(e instanceof Error ? e.message : "Seed failed.")
    }
  }

  const onImportFromLocal = async () => {
    setError(null)
    const raw = typeof window !== "undefined" ? localStorage.getItem(LOCAL_SETTINGS_KEY) : null
    if (!raw) {
      setError("No local settings found to import.")
      return
    }
    try {
      const local = JSON.parse(raw) as LLMProvidersSettings
      if (!local.providerId || !local.model) {
        setError("Local settings are incomplete.")
        return
      }
      await createConfiguration(arcadia, {
        name: `Imported (${local.providerId})`,
        provider: local.providerId as LlmProvider,
        model: local.model,
        base_url: local.baseURL || null,
        secret_name: local.secretName || null,
      })
      await refresh()
    } catch (e) {
      setError(e instanceof Error ? e.message : "Import failed.")
    }
  }

  const hasLocalSettings =
    typeof window !== "undefined" && !!localStorage.getItem(LOCAL_SETTINGS_KEY)

  return (
    <Card>
      <CardHeader className="flex flex-row items-start justify-between gap-4">
        <div className="flex-1">
          <CardTitle>LLM configurations</CardTitle>
          <CardDescription>
            Server-persisted provider/model/secret/cost settings. Toggle the star to
            pick which one the Assistant uses on the next message.
          </CardDescription>
        </div>
        {usage ? (
          <div className="flex shrink-0 flex-col items-end rounded-md border bg-muted/40 px-3 py-2 text-right">
            <span className="text-[10px] uppercase tracking-wide text-muted-foreground">
              Spend (30d)
            </span>
            <span className="font-mono text-base font-semibold tabular-nums">
              {formatCost(usage.total_cost_cents ?? 0)}
            </span>
            <span className="text-[10px] text-muted-foreground">
              {(usage.total_requests ?? 0).toLocaleString()} req ·{" "}
              {(usage.total_tokens ?? 0).toLocaleString()} tok
            </span>
          </div>
        ) : null}
        <div className="flex shrink-0 gap-2">
          {hasLocalSettings && configs.length === 0 ? (
            <Button
              variant="outline"
              size="sm"
              onClick={onImportFromLocal}
              data-action="llm-config-import-local"
            >
              <Upload className="size-4" />
              Import local
            </Button>
          ) : null}
          <Button
            size="sm"
            onClick={() => setEditing("new")}
            data-action="llm-config-add"
          >
            <Plus className="size-4" />
            Add
          </Button>
        </div>
      </CardHeader>

      <CardContent className="flex flex-col gap-3">
        {error ? (
          <p className="text-sm text-destructive" role="alert">
            {error}
          </p>
        ) : null}

        {loading ? (
          <p className="py-4 text-sm text-muted-foreground">Loading…</p>
        ) : configs.length === 0 ? (
          <EmptyState onSeed={onSeed} onImport={hasLocalSettings ? onImportFromLocal : null} />
        ) : (
          <ul className="divide-y border-y">
            {configs.map((c) => (
              <ConfigRow
                key={c.id}
                config={c}
                spend={findSpend(usageByModel, c)}
                isActive={isActive(c)}
                onMakeActive={() => onMakeActive(c)}
                onToggleEnabled={() => onToggleEnabled(c)}
                onEdit={() => setEditing(c)}
                onDelete={() => onDelete(c)}
              />
            ))}
          </ul>
        )}
      </CardContent>

      {editing ? (
        <ConfigDialog
          existing={editing === "new" ? null : editing}
          catalog={catalog}
          secrets={secrets}
          onClose={() => setEditing(null)}
          onSave={onSave}
        />
      ) : null}
    </Card>
  )
}

// --- Empty state ---------------------------------------------------------

function EmptyState({
  onSeed,
  onImport,
}: {
  onSeed: () => void
  onImport: (() => void) | null
}) {
  return (
    <div className="flex flex-col items-center gap-3 rounded-md border border-dashed bg-muted/20 px-6 py-10 text-center">
      <Sparkles className="size-6 text-muted-foreground" />
      <div className="flex flex-col gap-1">
        <p className="text-sm font-medium">No configurations yet</p>
        <p className="text-xs text-muted-foreground">
          Seed a starter set from the curated catalog (GPT-4o, Claude, DeepSeek, LM Studio)
          and tweak from there.
        </p>
      </div>
      <div className="flex gap-2">
        <Button size="sm" onClick={onSeed} data-action="llm-config-seed">
          <Sparkles className="size-4" />
          Seed from catalog
        </Button>
        {onImport ? (
          <Button variant="outline" size="sm" onClick={onImport}>
            <Upload className="size-4" />
            Import local
          </Button>
        ) : null}
      </div>
    </div>
  )
}

// --- Single row ----------------------------------------------------------

function ConfigRow({
  config: c,
  spend,
  isActive,
  onMakeActive,
  onToggleEnabled,
  onEdit,
  onDelete,
}: {
  config: LlmConfiguration
  spend: UsageByModelRow | undefined
  isActive: boolean
  onMakeActive: () => void
  onToggleEnabled: () => void
  onEdit: () => void
  onDelete: () => void
}) {
  return (
    <li className="flex items-center justify-between gap-3 px-1 py-2.5">
      <div className="flex min-w-0 items-center gap-3">
        <button
          type="button"
          onClick={onMakeActive}
          className="shrink-0 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
          aria-label={isActive ? "Active configuration" : "Make active"}
          data-action={`llm-config-activate-${c.id}`}
          title={isActive ? "Currently active for this browser" : "Make active for this browser"}
        >
          <Star
            className={`size-4 ${isActive ? "fill-amber-400 text-amber-400" : ""}`}
            aria-hidden
          />
        </button>

        <div className="flex min-w-0 flex-col gap-0.5">
          <span className="flex items-center gap-2 text-sm font-medium">
            <span className="truncate">{c.name}</span>
            {c.tenant_id == null ? (
              <span className="rounded-full bg-muted px-2 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground">
                platform
              </span>
            ) : null}
            {!c.enabled ? (
              <span className="text-[11px] text-muted-foreground">disabled</span>
            ) : null}
          </span>
          <span className="truncate text-xs text-muted-foreground">
            {c.provider} · <code className="font-mono">{c.model}</code>
            {c.secret_name ? (
              <>
                {" "}
                · secret <code className="font-mono">{c.secret_name}</code>
              </>
            ) : null}
          </span>
          <span className="text-[11px] text-muted-foreground">
            {formatRate(c.input_cost_per_million)}/1M in ·{" "}
            {formatRate(c.output_cost_per_million)}/1M out
          </span>
        </div>
      </div>

      <div className="flex shrink-0 items-center gap-3">
        {spend && spend.cost_cents > 0 ? (
          <div className="flex flex-col items-end text-right">
            <span className="font-mono text-sm tabular-nums">
              {formatCost(spend.cost_cents)}
            </span>
            <span className="text-[10px] text-muted-foreground">
              30d · {spend.requests.toLocaleString()} req
            </span>
          </div>
        ) : null}

        <div className="flex items-center gap-1">
          <Switch
            checked={c.enabled}
            onCheckedChange={onToggleEnabled}
            size="sm"
            aria-label={c.enabled ? "Disable" : "Enable"}
            data-action={`llm-config-enabled-${c.id}`}
          />
          <Button
            variant="ghost"
            size="sm"
            onClick={onEdit}
            data-action={`llm-config-edit-${c.id}`}
            aria-label="Edit"
            title={c.tenant_id == null ? "Edit platform default (visible to all tenants)" : "Edit"}
          >
            <Pencil className="size-4" />
          </Button>
          <Button
            variant="ghost"
            size="sm"
            onClick={onDelete}
            data-action={`llm-config-delete-${c.id}`}
            aria-label="Delete"
            title={c.tenant_id == null ? "Delete platform default" : "Delete"}
          >
            <Trash2 className="size-4" />
          </Button>
        </div>
      </div>
    </li>
  )
}

// --- Add/Edit modal ------------------------------------------------------

function ConfigDialog({
  existing,
  catalog,
  secrets,
  onClose,
  onSave,
}: {
  existing: LlmConfiguration | null
  catalog: CatalogEntry[]
  secrets: Secret[]
  onClose: () => void
  onSave: (
    input: LlmConfigurationInput,
    existing: LlmConfiguration | null,
  ) => Promise<void>
}) {
  const [draft, setDraft] = useState<LlmConfigurationInput>(
    existing
      ? {
          name: existing.name,
          provider: existing.provider,
          model: existing.model,
          base_url: existing.base_url,
          secret_name: existing.secret_name,
          input_cost_per_million: existing.input_cost_per_million,
          output_cost_per_million: existing.output_cost_per_million,
          enabled: existing.enabled,
        }
      : emptyDraft(),
  )
  const [saving, setSaving] = useState(false)
  const [err, setErr] = useState<string | null>(null)

  const modelsForProvider = useMemo(
    () => catalog.filter((c) => c.provider === draft.provider && c.model !== "*"),
    [catalog, draft.provider],
  )

  const onSubmit = async () => {
    setSaving(true)
    setErr(null)
    try {
      await onSave(draft, existing)
    } catch (e) {
      setErr(e instanceof Error ? e.message : "Save failed.")
    } finally {
      setSaving(false)
    }
  }

  const valid = draft.name.trim() !== "" && draft.model.trim() !== ""

  return (
    <Dialog open onOpenChange={(open) => !open && onClose()}>
      <DialogContent className="sm:max-w-[600px]">
        <DialogHeader>
          <DialogTitle>{existing ? "Edit configuration" : "New configuration"}</DialogTitle>
          <DialogDescription>
            Costs auto-fill from the curated catalog when you pick a known model.
            Override below if you have a negotiated rate.
          </DialogDescription>
        </DialogHeader>

        <div className="grid gap-3 py-2 sm:grid-cols-2">
          <Field label="Name" className="sm:col-span-2">
            <Input
              value={draft.name}
              onChange={(e) => setDraft({ ...draft, name: e.target.value })}
              placeholder="Production GPT-4o-mini"
              autoFocus={!existing}
            />
          </Field>

          <Field label="Provider">
            <Select
              value={draft.provider}
              onValueChange={(v) =>
                setDraft({ ...draft, provider: v as LlmProvider, model: "" })
              }
            >
              <SelectTrigger>
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {PROVIDERS.map((p) => (
                  <SelectItem key={p} value={p}>
                    {p}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </Field>

          <Field label="Model">
            <ModelPicker
              value={draft.model}
              models={modelsForProvider}
              onChange={(model) => {
                // Auto-fill costs when picking a catalog model.
                const entry = modelsForProvider.find((m) => m.model === model)
                setDraft({
                  ...draft,
                  model,
                  ...(entry && {
                    input_cost_per_million: entry.input_cost_per_million,
                    output_cost_per_million: entry.output_cost_per_million,
                  }),
                })
              }}
            />
          </Field>

          <Field label="Vault secret (optional)" className="sm:col-span-2">
            <SecretPicker
              value={draft.secret_name ?? null}
              secrets={secrets}
              onChange={(name) => setDraft({ ...draft, secret_name: name })}
            />
          </Field>

          <Field label="Base URL (optional)" className="sm:col-span-2">
            <Input
              value={draft.base_url ?? ""}
              onChange={(e) => setDraft({ ...draft, base_url: e.target.value || null })}
              placeholder="leave blank for provider default"
            />
          </Field>

          <Field label="Input cost (USD per 1M tokens)">
            <Input
              type="number"
              step="0.01"
              min={0}
              value={draft.input_cost_per_million ?? ""}
              onChange={(e) =>
                setDraft({
                  ...draft,
                  input_cost_per_million: e.target.value === "" ? null : Number(e.target.value),
                })
              }
              placeholder="0.15"
            />
          </Field>

          <Field label="Output cost (USD per 1M tokens)">
            <Input
              type="number"
              step="0.01"
              min={0}
              value={draft.output_cost_per_million ?? ""}
              onChange={(e) =>
                setDraft({
                  ...draft,
                  output_cost_per_million: e.target.value === "" ? null : Number(e.target.value),
                })
              }
              placeholder="0.60"
            />
          </Field>
        </div>

        {err ? (
          <p className="text-sm text-destructive" role="alert">
            {err}
          </p>
        ) : null}

        <DialogFooter>
          <Button variant="ghost" onClick={onClose} disabled={saving}>
            Cancel
          </Button>
          <Button
            onClick={onSubmit}
            disabled={!valid || saving}
            data-action="llm-config-save"
          >
            {saving ? "Saving…" : existing ? "Save changes" : "Create"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

// --- Model picker (Select + Custom… escape) -------------------------------

function ModelPicker({
  value,
  models,
  onChange,
}: {
  value: string
  models: CatalogEntry[]
  onChange: (model: string) => void
}) {
  const known = useMemo(() => new Set(models.map((m) => m.model)), [models])
  const isCustom = value !== "" && !known.has(value)
  const [customMode, setCustomMode] = useState(isCustom)

  useEffect(() => {
    if (!isCustom) setCustomMode(false)
  }, [models, isCustom])

  if (customMode) {
    return (
      <div className="flex gap-2">
        <Input
          value={value}
          onChange={(e) => onChange(e.target.value)}
          placeholder="custom-model-id"
          autoFocus
        />
        <Button
          type="button"
          variant="ghost"
          size="sm"
          onClick={() => {
            setCustomMode(false)
            onChange("")
          }}
        >
          Catalog
        </Button>
      </div>
    )
  }

  return (
    <Select
      value={known.has(value) ? value : ""}
      onValueChange={(v) => {
        if (v === "__custom__") {
          setCustomMode(true)
          onChange("")
        } else {
          onChange(v)
        }
      }}
    >
      <SelectTrigger>
        <SelectValue placeholder={models.length ? "Pick a model…" : "Type a model id"} />
      </SelectTrigger>
      <SelectContent>
        {models.map((m) => (
          <SelectItem key={m.model} value={m.model}>
            <span className="flex items-center justify-between gap-3">
              <span>{m.model}</span>
              <span className="text-[10px] text-muted-foreground">
                ${m.input_cost_per_million.toFixed(2)} / ${m.output_cost_per_million.toFixed(2)} per 1M
              </span>
            </span>
          </SelectItem>
        ))}
        <SelectItem value="__custom__">
          <span className="text-muted-foreground">Custom…</span>
        </SelectItem>
      </SelectContent>
    </Select>
  )
}

/**
 * Secret picker: Select populated from /api/v1/admin/secrets, filtered to
 * api_key category (LLM keys live there). Includes a "(none)" option for
 * keyless providers (lmstudio) and "Type a name…" for secrets that haven't
 * been created yet — the latter switches to free-text and the user can
 * type any name; the proxy will fail loudly at request time if it's wrong.
 */
function SecretPicker({
  value,
  secrets,
  onChange,
}: {
  value: string | null
  secrets: Secret[]
  onChange: (name: string | null) => void
}) {
  const apiKeys = useMemo(
    () =>
      secrets
        .filter((s) => s.category === "api_key" && s.enabled)
        .sort((a, b) => a.name.localeCompare(b.name)),
    [secrets],
  )

  const known = useMemo(() => new Set(apiKeys.map((s) => s.name)), [apiKeys])
  const isCustom = value != null && value !== "" && !known.has(value)
  const [customMode, setCustomMode] = useState(isCustom)

  useEffect(() => {
    if (!isCustom) setCustomMode(false)
  }, [secrets, isCustom])

  if (customMode) {
    return (
      <div className="flex gap-2">
        <Input
          value={value ?? ""}
          onChange={(e) => onChange(e.target.value || null)}
          placeholder="secret-name-not-yet-in-vault"
          autoFocus
        />
        <Button
          type="button"
          variant="ghost"
          size="sm"
          onClick={() => {
            setCustomMode(false)
            onChange(null)
          }}
        >
          Pick
        </Button>
      </div>
    )
  }

  // Encode null as the empty string for the Select — Radix/base-ui can't
  // bind an actual null/undefined value cleanly.
  const NONE = "__none__"
  const CUSTOM = "__custom__"

  return (
    <Select
      value={value == null ? NONE : known.has(value) ? value : ""}
      onValueChange={(v) => {
        if (v === NONE) onChange(null)
        else if (v === CUSTOM) {
          setCustomMode(true)
          onChange("")
        } else onChange(v)
      }}
    >
      <SelectTrigger>
        <SelectValue placeholder={apiKeys.length ? "Pick a secret…" : "No api_key secrets yet"} />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value={NONE}>
          <span className="text-muted-foreground">(none — keyless / local)</span>
        </SelectItem>
        {apiKeys.map((s) => (
          <SelectItem key={s.id} value={s.name}>
            <span className="flex flex-col items-start">
              <span className="font-mono text-xs">{s.name}</span>
              {s.description ? (
                <span className="text-[10px] text-muted-foreground">{s.description}</span>
              ) : null}
            </span>
          </SelectItem>
        ))}
        <SelectItem value={CUSTOM}>
          <span className="text-muted-foreground">Type a name…</span>
        </SelectItem>
      </SelectContent>
    </Select>
  )
}

function Field({
  label,
  children,
  className = "",
}: {
  label: string
  children: React.ReactNode
  className?: string
}) {
  return (
    <div className={`flex flex-col gap-1 ${className}`}>
      <Label className="text-xs">{label}</Label>
      {children}
    </div>
  )
}

function emptyDraft(): LlmConfigurationInput {
  return {
    name: "",
    provider: "openai",
    model: "",
    base_url: null,
    secret_name: null,
  }
}

function formatRate(rate: number | null): string {
  if (rate == null) return "—"
  if (rate === 0) return "free"
  return `$${rate.toFixed(2)}`
}
