"use client"

import * as React from "react"
import { toast } from "sonner"

import {
  createAssetClassDetectRuleClient,
  deleteAssetClassDetectRuleClient,
  listAssetClassDetectOperators,
  listAssetClassDetectRules,
  previewAssetClassDetectClient,
  reorderAssetClassDetectRulesClient,
  updateAssetClassDetectRuleClient,
  type AssetClassDetectOperators,
  type AssetClassDetectRuleRow,
} from "@/components/xml-apis/asset-class-detect/api/asset-class-detect.api"
import { idsAfterMove, positionInClass } from "@/components/xml-apis/shared/detect-rule-order"
import { Button } from "@/components/ui/button"
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 { cn } from "@/lib/utils"

const ASSET_CLASS_LABELS: Record<string, string> = {
  bond: "Bond",
  structure: "Structure",
  equity: "Equity",
  bond_fund: "Bond Fund",
  equity_fund: "Equity Fund",
  mixed_fund: "Mixed Fund",
  cash: "Cash",
  call: "Call",
  fd: "Fixed Deposit",
  od: "Overdraft",
  loan: "Loan",
  commodity: "Commodity",
  crypto: "Crypto",
  interest: "Interest / Dividend",
  alternative_investment: "Alternative Investment",
  equity_accumulator: "Equity Accumulator",
  equity_option: "Equity Option",
}

function formatClassLabel(assetClass: string): string {
  return (
    ASSET_CLASS_LABELS[assetClass] ??
    assetClass
      .split("_")
      .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
      .join(" ")
  )
}

const ALL_RULES_VIEW = "__all__"
const VALUE_OPTIONAL_OPS = new Set([
  "present",
  "absent",
  "present_or_embedded_date",
  "coupon_in_name",
])

const EMPTY_OPERATORS: AssetClassDetectOperators = {
  fields: ["name"],
  operators: ["contains", "regex"],
  types: ["match", "reject"],
  rejectKinds: [],
  assetClasses: [],
  notes: [],
}

export function AssetClassDetectPage() {
  const [catalog, setCatalog] = React.useState<AssetClassDetectOperators>(EMPTY_OPERATORS)
  const [selectedClass, setSelectedClass] = React.useState<string>(ALL_RULES_VIEW)
  const [rules, setRules] = React.useState<AssetClassDetectRuleRow[]>([])
  const [allRules, setAllRules] = React.useState<AssetClassDetectRuleRow[]>([])
  const [allRulesFilter, setAllRulesFilter] = React.useState("")
  const [loading, setLoading] = React.useState(true)

  const [newRuleClass, setNewRuleClass] = React.useState("")
  const [newType, setNewType] = React.useState<"match" | "reject">("match")
  const [newPath, setNewPath] = React.useState("")
  const [newField, setNewField] = React.useState("name")
  const [newOperator, setNewOperator] = React.useState("contains")
  const [newValue, setNewValue] = React.useState("")
  const [newRejectKind, setNewRejectKind] = React.useState("")
  const [newLabel, setNewLabel] = React.useState("")

  const [previewName, setPreviewName] = React.useState("")
  const [previewIsin, setPreviewIsin] = React.useState("")
  const [previewPrice, setPreviewPrice] = React.useState("")
  const [previewMaturity, setPreviewMaturity] = React.useState("")
  const [previewResult, setPreviewResult] = React.useState("")

  const isAllView = selectedClass === ALL_RULES_VIEW
  const assetClasses = catalog.assetClasses
  const activeRuleClass = isAllView ? newRuleClass : selectedClass

  // "Apply" functions take an already-fetched result, so an effect can call them from a promise
  // callback (not synchronously); the reload functions are the same thing for event handlers.
  const applyRules = React.useCallback(
    (result: Awaited<ReturnType<typeof listAssetClassDetectRules>>) => {
      if (result.errorMessage) {
        toast.error(result.errorMessage)
        return
      }
      setRules(result.rules)
    },
    [],
  )

  const applyAllRules = React.useCallback(
    (result: Awaited<ReturnType<typeof listAssetClassDetectRules>>) => {
      if (result.errorMessage) {
        toast.error(result.errorMessage)
        return
      }
      setAllRules(result.rules)
      if (result.assetClasses.length > 0) {
        setCatalog((prev) =>
          prev.assetClasses.length > 0 ? prev : { ...prev, assetClasses: result.assetClasses },
        )
      }
    },
    [],
  )

  const reloadRules = React.useCallback(
    async (assetClass: string) => applyRules(await listAssetClassDetectRules(assetClass)),
    [applyRules],
  )

  const reloadAllRules = React.useCallback(
    async () => applyAllRules(await listAssetClassDetectRules()),
    [applyAllRules],
  )

  const refreshRules = React.useCallback(async () => {
    if (isAllView) {
      await reloadAllRules()
      return
    }
    if (!selectedClass) {
      setRules([])
      return
    }
    await reloadRules(selectedClass)
  }, [isAllView, reloadAllRules, reloadRules, selectedClass])

  React.useEffect(() => {
    let cancelled = false
    ;(async () => {
      const ops = await listAssetClassDetectOperators()
      if (cancelled) return
      if (ops.errorMessage) toast.error(ops.errorMessage)
      const next = ops.operators.assetClasses.length ? ops.operators : EMPTY_OPERATORS
      if (ops.operators.assetClasses.length) {
        setCatalog(ops.operators)
      }
      setNewRuleClass((prev) => prev || next.assetClasses[0] || "")
      setNewField((prev) => prev || next.fields[0] || "name")
      setNewOperator((prev) => prev || next.operators[0] || "contains")
      await reloadAllRules()
      if (!cancelled) setLoading(false)
    })()
    return () => {
      cancelled = true
    }
  }, [reloadAllRules])

  // Picking a class also makes it the class a new rule is added to. Adjusting state while
  // rendering (not in an effect) avoids a second, cascading render.
  const [syncedClass, setSyncedClass] = React.useState<string | null>(null)
  if (selectedClass !== syncedClass) {
    setSyncedClass(selectedClass)
    if (selectedClass && selectedClass !== ALL_RULES_VIEW) {
      setNewRuleClass(selectedClass)
    }
  }

  React.useEffect(() => {
    if (!selectedClass) return
    if (selectedClass === ALL_RULES_VIEW) {
      void listAssetClassDetectRules().then(applyAllRules)
      return
    }
    void listAssetClassDetectRules(selectedClass).then(applyRules)
  }, [selectedClass, applyRules, applyAllRules])

  const filteredAllRules = React.useMemo(() => {
    const q = allRulesFilter.trim().toLowerCase()
    if (!q) return allRules
    return allRules.filter((rule) => {
      const hay = [
        rule.asset_class,
        formatClassLabel(rule.asset_class),
        rule.type,
        rule.path_code,
        rule.field,
        rule.operator,
        rule.value ?? "",
        rule.reject_kind ?? "",
        rule.label ?? "",
      ]
        .join(" ")
        .toLowerCase()
      return hay.includes(q)
    })
  }, [allRules, allRulesFilter])

  async function onAddRule() {
    if (!activeRuleClass) {
      toast.error("Asset class is required")
      return
    }
    if (newType === "reject" && !newRejectKind.trim()) {
      toast.error("reject_kind is required for reject rules")
      return
    }
    if (!VALUE_OPTIONAL_OPS.has(newOperator) && !newValue.trim()) {
      toast.error("Value is required for this operator")
      return
    }
    const result = await createAssetClassDetectRuleClient({
      asset_class: activeRuleClass,
      type: newType,
      path_code: newPath.trim(),
      field: newField,
      operator: newOperator,
      value: newValue.trim(),
      reject_kind: newType === "reject" ? newRejectKind.trim() : "",
      enabled: true,
      label: newLabel.trim() || `${newOperator} ${newValue.trim()}`.trim(),
    })
    if (result.errorMessage || !result.rule) {
      toast.error(result.errorMessage ?? "Create failed")
      return
    }
    toast.success("Rule created")
    setNewValue("")
    setNewLabel("")
    await refreshRules()
  }

  async function onDeleteRule(id: number) {
    const result = await deleteAssetClassDetectRuleClient(id)
    if (result.errorMessage) {
      toast.error(result.errorMessage)
      return
    }
    toast.success("Rule deleted")
    await refreshRules()
  }

  async function onMove(id: number, direction: -1 | 1, assetClass: string) {
    const ordered = idsAfterMove(isAllView ? allRules : rules, id, direction, assetClass)
    if (!ordered) return
    const result = await reorderAssetClassDetectRulesClient(assetClass, ordered)
    if (result.errorMessage) {
      toast.error(result.errorMessage)
      return
    }
    if (isAllView) {
      await reloadAllRules()
    } else {
      setRules(result.rules)
    }
  }

  async function onToggleRule(rule: AssetClassDetectRuleRow) {
    const result = await updateAssetClassDetectRuleClient({
      id: rule.id,
      enabled: !rule.enabled,
    })
    if (result.errorMessage) {
      toast.error(result.errorMessage)
      return
    }
    await refreshRules()
  }

  async function onPreview() {
    if (!previewName.trim() && !previewIsin.trim()) {
      toast.error("Enter a sample name or ISIN")
      return
    }
    const result = await previewAssetClassDetectClient({
      rows: [
        {
          name: previewName.trim(),
          isin: previewIsin.trim(),
          price: previewPrice.trim() === "" ? null : Number(previewPrice),
          maturity: previewMaturity.trim(),
        },
      ],
    })
    if (result.errorMessage) {
      toast.error(result.errorMessage)
      return
    }
    const v = result.verdicts[0]
    if (!v) {
      setPreviewResult("No verdict")
      return
    }
    const matched = Array.isArray(v.matchedRules) ? v.matchedRules.length : 0
    const klass = v.assetClass ? formatClassLabel(v.assetClass) : "Untagged"
    setPreviewResult(
      `${klass}${v.kind ? ` (${v.kind})` : ""} — ${v.reason || "no_rule_match"}${
        matched ? ` · ${matched} matched rule(s)` : ""
      }`,
    )
  }

  if (loading) {
    return <div className="p-6 text-sm text-muted-foreground">Loading asset class detect…</div>
  }

  function renderAddRuleForm(includeClassSelect: boolean) {
    return (
      <div className="space-y-2 rounded-md border p-3">
        <div
          className={cn(
            "grid gap-2",
            includeClassSelect ? "md:grid-cols-4" : "md:grid-cols-3",
          )}
        >
          {includeClassSelect ? (
            <div className="space-y-1">
              <Label>Asset Class</Label>
              <Select value={newRuleClass} onValueChange={setNewRuleClass}>
                <SelectTrigger>
                  <SelectValue placeholder="Class" />
                </SelectTrigger>
                <SelectContent>
                  {assetClasses.map((c) => (
                    <SelectItem key={c} value={c}>
                      {formatClassLabel(c)}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          ) : null}
          <div className="space-y-1">
            <Label>Type</Label>
            <Select
              value={newType}
              onValueChange={(v) => setNewType(v as "match" | "reject")}
            >
              <SelectTrigger>
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {(catalog.types.length ? catalog.types : ["match", "reject"]).map((t) => (
                  <SelectItem key={t} value={t}>
                    {t}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="space-y-1">
            <Label>Path</Label>
            <Input
              value={newPath}
              onChange={(e) => setNewPath(e.target.value)}
              placeholder="same path = AND"
            />
          </div>
          <div className="space-y-1">
            <Label>Field</Label>
            <Select value={newField} onValueChange={setNewField}>
              <SelectTrigger>
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {(catalog.fields.length ? catalog.fields : ["name"]).map((f) => (
                  <SelectItem key={f} value={f}>
                    {f}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="space-y-1">
            <Label>Operator</Label>
            <Select value={newOperator} onValueChange={setNewOperator}>
              <SelectTrigger>
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {(catalog.operators.length ? catalog.operators : ["contains"]).map((op) => (
                  <SelectItem key={op} value={op}>
                    {op}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>
          <div className="space-y-1">
            <Label>Value</Label>
            <Input
              value={newValue}
              onChange={(e) => setNewValue(e.target.value)}
              placeholder={
                newOperator === "numeric_between"
                  ? '{"min":0,"max":200}'
                  : newOperator === "numeric_not_in"
                    ? '{"values":[100]}'
                    : VALUE_OPTIONAL_OPS.has(newOperator)
                      ? "(optional)"
                      : "e.g. ETF"
              }
            />
          </div>
          {newType === "reject" ? (
            <div className="space-y-1">
              <Label>Reject kind</Label>
              <Select value={newRejectKind} onValueChange={setNewRejectKind}>
                <SelectTrigger>
                  <SelectValue placeholder="Kind" />
                </SelectTrigger>
                <SelectContent>
                  {catalog.rejectKinds.map((k) => (
                    <SelectItem key={k} value={k}>
                      {k}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          ) : null}
          <div className="space-y-1">
            <Label>Label</Label>
            <Input
              value={newLabel}
              onChange={(e) => setNewLabel(e.target.value)}
              placeholder="optional"
            />
          </div>
          <div className="flex items-end">
            <Button className="w-full" onClick={() => void onAddRule()}>
              Add rule
            </Button>
          </div>
        </div>
      </div>
    )
  }

  function renderRulesTable(
    rows: AssetClassDetectRuleRow[],
    options: { showClass: boolean },
  ) {
    const classOrderIndex = (rule: AssetClassDetectRuleRow) =>
      positionInClass(options.showClass ? allRules : rules, rule)

    const colSpan = options.showClass ? 11 : 10

    return (
      <div className="overflow-hidden rounded-md border">
        <table className="w-full text-sm">
          <thead className="bg-muted/50 text-left">
            <tr>
              {options.showClass ? <th className="px-3 py-2">Asset Class</th> : null}
              <th className="px-3 py-2">#</th>
              <th className="px-3 py-2">Type</th>
              <th className="px-3 py-2">Path</th>
              <th className="px-3 py-2">Field</th>
              <th className="px-3 py-2">Operator</th>
              <th className="px-3 py-2">Value</th>
              <th className="px-3 py-2">Reject</th>
              <th className="px-3 py-2">On</th>
              <th className="px-3 py-2">Order</th>
              <th className="px-3 py-2" />
            </tr>
          </thead>
          <tbody>
            {rows.length === 0 ? (
              <tr>
                <td colSpan={colSpan} className="px-3 py-4 text-muted-foreground">
                  No rules yet — add one above.
                </td>
              </tr>
            ) : (
              rows.map((rule) => {
                const { index, total } = classOrderIndex(rule)
                return (
                  <tr key={rule.id} className="border-t">
                    {options.showClass ? (
                      <td className="px-3 py-2">
                        <button
                          type="button"
                          className="text-left hover:underline"
                          onClick={() => setSelectedClass(rule.asset_class)}
                          title="Focus this class"
                        >
                          {formatClassLabel(rule.asset_class)}
                        </button>
                      </td>
                    ) : null}
                    <td className="px-3 py-2">{index + 1}</td>
                    <td className="px-3 py-2 font-mono text-xs">{rule.type}</td>
                    <td className="px-3 py-2 font-mono text-xs">{rule.path_code || "—"}</td>
                    <td className="px-3 py-2 font-mono text-xs">{rule.field}</td>
                    <td className="px-3 py-2 font-mono text-xs">{rule.operator}</td>
                    <td className="max-w-[14rem] truncate px-3 py-2 font-mono text-xs" title={rule.value ?? ""}>
                      {rule.value || "—"}
                    </td>
                    <td className="px-3 py-2 font-mono text-xs">{rule.reject_kind || "—"}</td>
                    <td className="px-3 py-2">
                      <Switch
                        checked={rule.enabled}
                        onCheckedChange={() => void onToggleRule(rule)}
                      />
                    </td>
                    <td className="px-3 py-2">
                      <div className="flex gap-1">
                        <Button
                          type="button"
                          size="sm"
                          variant="outline"
                          disabled={index <= 0}
                          onClick={() => void onMove(rule.id, -1, rule.asset_class)}
                        >
                          Up
                        </Button>
                        <Button
                          type="button"
                          size="sm"
                          variant="outline"
                          disabled={index < 0 || index >= total - 1}
                          onClick={() => void onMove(rule.id, 1, rule.asset_class)}
                        >
                          Down
                        </Button>
                      </div>
                    </td>
                    <td className="px-3 py-2">
                      <Button
                        type="button"
                        size="sm"
                        variant="ghost"
                        onClick={() => void onDeleteRule(rule.id)}
                      >
                        Delete
                      </Button>
                    </td>
                  </tr>
                )
              })
            )}
          </tbody>
        </table>
      </div>
    )
  }

  function renderPreview() {
    return (
      <section className="space-y-2 rounded-md border p-3">
        <h2 className="text-sm font-semibold">Preview sample row</h2>
        <p className="text-xs text-muted-foreground">
          Combined Common Rules engine (class competition). Locked Master is not applied here.
        </p>
        <div className="flex flex-wrap gap-2">
          <Input
            className="min-w-[220px] flex-1"
            placeholder="Name"
            value={previewName}
            onChange={(e) => setPreviewName(e.target.value)}
          />
          <Input
            className="min-w-[160px]"
            placeholder="ISIN"
            value={previewIsin}
            onChange={(e) => setPreviewIsin(e.target.value)}
          />
          <Input
            className="w-[120px]"
            placeholder="Price"
            value={previewPrice}
            onChange={(e) => setPreviewPrice(e.target.value)}
          />
          <Input
            className="w-[140px]"
            placeholder="Maturity"
            value={previewMaturity}
            onChange={(e) => setPreviewMaturity(e.target.value)}
          />
          <Button variant="secondary" onClick={() => void onPreview()}>
            Preview
          </Button>
        </div>
        {previewResult ? (
          <p className="text-sm">
            Result: <span className="font-medium">{previewResult}</span>
          </p>
        ) : null}
      </section>
    )
  }

  return (
    <div className="flex h-full min-h-[70vh] gap-4 p-4">
      <aside className="w-56 shrink-0 space-y-2 border-r pr-3">
        <h2 className="text-sm font-semibold">Views</h2>
        <div className="space-y-1">
          <button
            type="button"
            onClick={() => setSelectedClass(ALL_RULES_VIEW)}
            className={cn(
              "w-full rounded-md px-2 py-1.5 text-left text-sm font-medium",
              isAllView ? "bg-primary text-primary-foreground" : "hover:bg-muted",
            )}
          >
            All Rules
          </button>
          <div className="my-2 border-t" />
          <p className="px-1 text-xs text-muted-foreground">Focus one class</p>
          {assetClasses.map((c) => (
            <button
              key={c}
              type="button"
              onClick={() => setSelectedClass(c)}
              className={cn(
                "w-full rounded-md px-2 py-1.5 text-left text-sm",
                selectedClass === c
                  ? "bg-primary text-primary-foreground"
                  : "hover:bg-muted",
              )}
            >
              {formatClassLabel(c)}
            </button>
          ))}
        </div>
      </aside>

      <main className="min-w-0 flex-1 space-y-6">
        {isAllView ? (
          <div className="space-y-4">
            <div className="space-y-2">
              <h1 className="text-xl font-semibold">Asset Class Detect</h1>
              <p className="text-sm text-muted-foreground">
                Common Rules that classify Asset Class on XML Detect. Match paths AND within
                the same path_code; reject rules run first.
              </p>
            </div>
            {renderAddRuleForm(true)}
            <div className="flex flex-wrap items-center gap-2">
              <Input
                className="max-w-sm"
                placeholder="Filter by class, field, value…"
                value={allRulesFilter}
                onChange={(e) => setAllRulesFilter(e.target.value)}
              />
              <span className="text-xs text-muted-foreground">
                {filteredAllRules.length} rule{filteredAllRules.length === 1 ? "" : "s"}
              </span>
            </div>
            {renderRulesTable(filteredAllRules, { showClass: true })}
            {renderPreview()}
          </div>
        ) : (
          <>
            <div className="space-y-3">
              <h1 className="text-xl font-semibold">{formatClassLabel(selectedClass)}</h1>
              <p className="text-sm text-muted-foreground">
                Rules for this class. Same path_code conditions AND together.
              </p>
            </div>
            <section className="space-y-3">
              <h2 className="text-sm font-semibold">Rules</h2>
              {renderAddRuleForm(false)}
              {renderRulesTable(rules, { showClass: false })}
            </section>
            {renderPreview()}
          </>
        )}
      </main>
    </div>
  )
}
