"use client"

import Link from "next/link"
import { useMemo, useState } from "react"
import { ArrowUpRight, Bell, BellOff, RefreshCw, Save, Search, Undo2 } from "lucide-react"
import { toast } from "sonner"

import { ListPageCard } from "@/app/dashboard/_components"
import { formatRelativeTime } from "@/components/notifications/format-relative-time"
import { announceNotificationsChanged } from "@/components/notifications/notifications-changed-event"
import {
  draftFromTypes,
  filterTypes,
  groupByModule,
  hasErrors,
  isDirty,
  setModuleEnabled,
  summarizeEnabled,
  thresholdError,
  type SettingsDraft,
  type SettingsShow,
} from "@/components/notifications/settings-view"
import type { NotificationSeverity, NotificationTypeSetting } from "@/components/notifications/types"
import { useNowMs } from "@/components/notifications/use-now"
import { ErrorBanner } from "@/components/shared/error-banner"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"
import { Input } from "@/components/ui/input"
import { Switch } from "@/components/ui/switch"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { FRONTEND_ROUTES } from "@/config/frontend-routes"
import { dashboardCsrfHeader } from "@/lib/csrf.client"
import { cn } from "@/lib/utils"

type Props = {
  initialTypes: NotificationTypeSetting[]
  initialErrorMessage?: string | null
}

type ApiResult = { status?: string; message?: string; data?: { types?: NotificationTypeSetting[] } }

const SEVERITY_BADGE: Record<NotificationSeverity, { label: string; className: string }> = {
  critical: {
    label: "Critical",
    className: "border-rose-200 bg-rose-50 text-rose-800 dark:border-rose-900 dark:bg-rose-950/40 dark:text-rose-300",
  },
  warning: {
    label: "Warning",
    className: "border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-900 dark:bg-amber-950/40 dark:text-amber-300",
  },
  info: { label: "Info", className: "border-border bg-muted/50 text-muted-foreground" },
}

export function NotificationSettingsPage({ initialTypes, initialErrorMessage = null }: Props) {
  const nowMs = useNowMs()
  const [types, setTypes] = useState(initialTypes)
  const [draft, setDraft] = useState<SettingsDraft>(() => draftFromTypes(initialTypes))
  const [query, setQuery] = useState("")
  const [show, setShow] = useState<SettingsShow>("all")
  const [error, setError] = useState(initialErrorMessage)
  const [busy, setBusy] = useState<"save" | "scan" | null>(null)

  const dirty = isDirty(types, draft)
  const invalid = hasErrors(types, draft)
  const { on, total } = summarizeEnabled(types, draft)
  const activeNow = useMemo(() => types.reduce((sum, t) => sum + (draft.enabled[t.type] ? t.activeCount : 0), 0), [types, draft])
  // Filter on what is saved, not the draft: switching a row off while "On" is selected must not make it vanish
  // under the cursor (there would be no way to undo it). The filter re-applies after Save.
  const saved = useMemo(() => draftFromTypes(types), [types])
  const modules = useMemo(() => groupByModule(filterTypes(types, saved, { query, show })), [types, saved, query, show])

  async function post(url: string, body: Record<string, unknown>): Promise<ApiResult | null> {
    try {
      const res = await fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json", ...dashboardCsrfHeader() },
        body: JSON.stringify(body),
      })
      const json = (await res.json().catch(() => ({}))) as ApiResult
      if (!res.ok || json.status === "error") {
        setError(json.message || "Request failed.")
        return null
      }
      return json
    } catch {
      setError("Could not reach the server.")
      return null
    }
  }

  async function save() {
    if (invalid || !dirty) return
    setBusy("save")
    setError(null)
    const thresholds: Record<string, number> = {}
    for (const t of types) if (t.threshold) thresholds[t.type] = Number(draft.thresholds[t.type])
    const json = await post(FRONTEND_ROUTES.notifications.settingsSubmit, { enabled: draft.enabled, thresholds })
    if (json?.data?.types) {
      setTypes(json.data.types)
      setDraft(draftFromTypes(json.data.types))
      toast.success("Notification settings saved")
      // Switching an alert off clears its live items from the bell straight away.
      announceNotificationsChanged()
    }
    setBusy(null)
  }

  async function scanNow() {
    setBusy("scan")
    setError(null)
    const json = await post(FRONTEND_ROUTES.notifications.scan, {})
    if (json) {
      toast.success(json.message || "Scan complete")
      announceNotificationsChanged()
      // Pull fresh "active now" numbers without touching unsaved edits.
      try {
        const res = await fetch(FRONTEND_ROUTES.notifications.settingsSubmit, { cache: "no-store", credentials: "same-origin" })
        const fresh = (await res.json()) as ApiResult
        const byType = new Map((fresh.data?.types ?? []).map((t) => [t.type, t]))
        setTypes((prev) =>
          prev.map((t) => {
            const f = byType.get(t.type)
            return f ? { ...t, activeCount: f.activeCount, lastRaisedAt: f.lastRaisedAt } : t
          })
        )
      } catch {
        // The scan itself worked; stale counters are the only cost.
      }
    }
    setBusy(null)
  }

  const setEnabled = (type: string, value: boolean) =>
    setDraft((d) => ({ ...d, enabled: { ...d.enabled, [type]: value } }))

  return (
    <ListPageCard
      icon={Bell}
      title="Notification settings"
      description="Choose which alerts reach the bell and when they fire. Alerts refresh every 15 minutes; turning one off clears its current items from the bell and keeps the history."
      breadcrumb={[{ label: "Notifications", href: FRONTEND_ROUTES.notifications.page }, { label: "Settings" }]}
      actions={
        <div className="flex items-center gap-2">
          <Button asChild variant="outline" size="sm">
            <Link href={FRONTEND_ROUTES.notifications.page}>View all notifications</Link>
          </Button>
          <Button type="button" variant="outline" size="sm" disabled={busy !== null} onClick={() => void scanNow()}>
            <RefreshCw className={cn("mr-1.5 size-3.5", busy === "scan" && "animate-spin")} />
            {busy === "scan" ? "Scanning…" : "Run scan now"}
          </Button>
        </div>
      }
    >
      <ErrorBanner message={error} className="mb-4" />

      {/* Overview + filters */}
      <div className="mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
        <p className="text-sm">
          <span className="font-semibold tabular-nums">
            {on}/{total}
          </span>{" "}
          <span className="text-muted-foreground">
            alerts on · {activeNow === 0 ? "none active right now" : `${activeNow} active right now`}
          </span>
        </p>
        <div className="flex items-center gap-2">
          <div className="relative w-full sm:w-56">
            <Search className="-translate-y-1/2 pointer-events-none absolute top-1/2 left-2.5 size-3.5 text-muted-foreground" />
            <Input
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              placeholder="Search alerts…"
              aria-label="Search alerts"
              className="h-8 pl-8 text-sm"
            />
          </div>
          <Tabs value={show} onValueChange={(v) => setShow(v as SettingsShow)}>
            <TabsList className="h-8">
              <TabsTrigger value="all" className="px-2.5 text-xs">
                All
              </TabsTrigger>
              <TabsTrigger value="on" className="px-2.5 text-xs">
                On
              </TabsTrigger>
              <TabsTrigger value="off" className="px-2.5 text-xs">
                Off
              </TabsTrigger>
            </TabsList>
          </Tabs>
        </div>
      </div>

      {/* Modules */}
      <div className="space-y-3">
        {modules.length === 0 ? (
          <Card className="shadow-none">
            <CardContent>
              <Empty>
                <EmptyHeader>
                  <EmptyMedia variant="icon">
                    <BellOff />
                  </EmptyMedia>
                  <EmptyTitle>{types.length === 0 ? "No alert types available" : "No alerts match"}</EmptyTitle>
                  <EmptyDescription>
                    {types.length === 0
                      ? "The notification service did not return any alert types."
                      : "Try a different search, or show all alerts."}
                  </EmptyDescription>
                </EmptyHeader>
                {types.length > 0 ? (
                  <Button
                    type="button"
                    variant="outline"
                    size="sm"
                    onClick={() => {
                      setQuery("")
                      setShow("all")
                    }}
                  >
                    Reset filters
                  </Button>
                ) : null}
              </Empty>
            </CardContent>
          </Card>
        ) : (
          modules.map(({ module, types: rows }) => {
            // The master switch and counts cover the whole module, not just the rows the filter left visible.
            const all = types.filter((t) => t.module === module)
            const onCount = all.filter((t) => draft.enabled[t.type]).length
            const link = all.find((t) => t.link)?.link ?? null
            return (
              <Card key={module} className="gap-0 py-0 shadow-none">
                <div className="flex items-center justify-between gap-3 border-b bg-muted/30 px-4 py-2">
                  <h3 className="min-w-0 truncate font-medium text-sm">
                    {module}
                    <span className="ml-2 font-normal text-muted-foreground text-xs tabular-nums">
                      {onCount}/{all.length} on
                    </span>
                  </h3>
                  <div className="flex items-center gap-3">
                    {link ? (
                      <Link href={link} className="flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground">
                        Open page
                        <ArrowUpRight className="size-3.5" />
                      </Link>
                    ) : null}
                    <Switch
                      size="sm"
                      checked={onCount === all.length}
                      onCheckedChange={(v) => setDraft((d) => setModuleEnabled(d, types, module, v))}
                      aria-label={`Switch every ${module} alert ${onCount === all.length ? "off" : "on"}`}
                    />
                  </div>
                </div>
                <ul className="divide-y">
                  {rows.map((t) => (
                    <SettingRow
                      key={t.type}
                      t={t}
                      enabled={Boolean(draft.enabled[t.type])}
                      threshold={draft.thresholds[t.type]}
                      nowMs={nowMs}
                      onToggle={(v) => setEnabled(t.type, v)}
                      onThreshold={(v) => setDraft((d) => ({ ...d, thresholds: { ...d.thresholds, [t.type]: v } }))}
                    />
                  ))}
                </ul>
              </Card>
            )
          })
        )}
      </div>

      {/* Save bar — only when there is something to save. */}
      {dirty ? (
        <div
          role="region"
          aria-label="Unsaved changes"
          className="sticky bottom-3 z-20 mt-4 flex flex-wrap items-center justify-between gap-3 rounded-xl border bg-background/95 px-3 py-2 shadow-lg backdrop-blur"
        >
          <p className={cn("text-sm", invalid ? "text-destructive" : "text-muted-foreground")}>
            {invalid ? "Fix the highlighted values to save." : "You have unsaved changes."}
          </p>
          <div className="flex items-center gap-2">
            <Button type="button" variant="ghost" size="sm" disabled={busy !== null} onClick={() => setDraft(draftFromTypes(types))}>
              <Undo2 className="mr-1.5 size-3.5" />
              Discard
            </Button>
            <Button type="button" size="sm" disabled={busy !== null || invalid} onClick={() => void save()}>
              <Save className="mr-1.5 size-3.5" />
              {busy === "save" ? "Saving…" : "Save changes"}
            </Button>
          </div>
        </div>
      ) : null}
    </ListPageCard>
  )
}

function SettingRow({
  t,
  enabled,
  threshold,
  nowMs,
  onToggle,
  onThreshold,
}: {
  t: NotificationTypeSetting
  enabled: boolean
  threshold: string | undefined
  nowMs: number | null
  onToggle: (value: boolean) => void
  onThreshold: (value: string) => void
}) {
  const switchId = `notif-${t.type}`
  const inputId = `notif-threshold-${t.type}`
  const error = thresholdError(t, threshold)
  const badge = SEVERITY_BADGE[t.severity]
  // "Last raised 3d ago" depends on the viewer's clock, so it waits for the client.
  const lastRaised = nowMs !== null && t.lastRaisedAt !== null ? formatRelativeTime(t.lastRaisedAt, nowMs) : ""

  return (
    <li className="flex flex-col gap-2 px-4 py-2.5 sm:flex-row sm:items-center sm:gap-4">
      <div className="flex min-w-0 flex-1 items-start gap-3">
        <Switch id={switchId} size="sm" className="mt-0.5" checked={enabled} onCheckedChange={onToggle} />
        <div className="min-w-0 flex-1">
          <div className="flex flex-wrap items-center gap-x-2 gap-y-0.5">
            <label htmlFor={switchId} className={cn("cursor-pointer font-medium text-sm leading-5", !enabled && "text-muted-foreground")}>
              {t.label}
            </label>
            <Badge variant="outline" className={cn("h-4 px-1.5 font-normal text-[11px]", badge.className)}>
              {badge.label}
            </Badge>
            {enabled && t.activeCount > 0 ? (
              <Badge variant="secondary" className="h-4 px-1.5 font-normal text-[11px]">
                {t.activeCount} active
              </Badge>
            ) : null}
            {lastRaised ? <span className="text-muted-foreground/80 text-[11px]">last raised {lastRaised}</span> : null}
          </div>
          {/* One line; the full text is on hover. */}
          <p title={t.description} className="truncate text-muted-foreground text-xs leading-5">
            {t.description}
          </p>
        </div>
      </div>

      {t.threshold ? (
        <div className="flex items-center gap-2 pl-10 sm:shrink-0 sm:pl-0">
          <label
            htmlFor={inputId}
            title={t.threshold.label}
            className={cn("max-w-44 truncate text-xs", enabled ? "text-muted-foreground" : "text-muted-foreground/60")}
          >
            {t.threshold.label}
          </label>
          <Input
            id={inputId}
            type="number"
            inputMode="numeric"
            min={t.threshold.min}
            max={t.threshold.max}
            value={threshold ?? ""}
            disabled={!enabled}
            aria-invalid={error !== null}
            aria-describedby={`${inputId}-hint`}
            onChange={(e) => onThreshold(e.target.value)}
            className="h-7 w-20 px-2 text-sm"
          />
          <span
            id={`${inputId}-hint`}
            className={cn("min-w-16 whitespace-nowrap text-[11px] tabular-nums", error ? "text-destructive" : "text-muted-foreground/70")}
          >
            {error ?? `${t.threshold.min}–${t.threshold.max}`}
          </span>
        </div>
      ) : null}
    </li>
  )
}
