"use client"

import Link from "next/link"
import { useRef, useState } from "react"
import {
  ArrowUpRight,
  Bell,
  BellOff,
  CheckCheck,
  CircleCheck,
  Info,
  OctagonAlert,
  Search,
  Settings,
  TriangleAlert,
  X,
} from "lucide-react"

import { ListPageCard } from "@/app/dashboard/_components"
import { historyQueryString } from "@/app/dashboard/notifications/_lib/notifications-filters"
import {
  alertClockTime,
  alertDayLabel,
  alertFullDateTime,
  alertLastedFor,
} from "@/components/notifications/alert-time"
import { formatRelativeTime } from "@/components/notifications/format-relative-time"
import { groupAlertsByDay, hasActiveFilters } from "@/components/notifications/history-view"
import { announceNotificationsChanged } from "@/components/notifications/notifications-changed-event"
import type {
  BackendNotification,
  NotificationHistory,
  NotificationHistoryFilters,
  NotificationSeverity,
} 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 { PageSizeSelect, PageStepButtons } from "@/components/ui/pagination-controls"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
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"

const ANY = "__any__"
const PAGE_SIZES = [10, 20, 50, 100] as const
const BODY_COLLAPSE_CHARS = 110

const SEVERITY: Record<NotificationSeverity, { icon: typeof Info; label: string; chip: string; badge: string }> = {
  critical: {
    icon: OctagonAlert,
    label: "Critical",
    chip: "bg-destructive/10 text-destructive",
    badge: "border-rose-200 bg-rose-50 text-rose-800 dark:border-rose-900 dark:bg-rose-950/40 dark:text-rose-300",
  },
  warning: {
    icon: TriangleAlert,
    label: "Warning",
    chip: "bg-amber-500/10 text-amber-600",
    badge: "border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-900 dark:bg-amber-950/40 dark:text-amber-300",
  },
  info: {
    icon: Info,
    label: "Info",
    chip: "bg-muted text-muted-foreground",
    badge: "border-border bg-muted/50 text-muted-foreground",
  },
}

type Props = {
  initialHistory: NotificationHistory
  initialErrorMessage?: string | null
}

async function postJson(url: string, body: Record<string, unknown> = {}): Promise<boolean> {
  try {
    const res = await fetch(url, {
      method: "POST",
      headers: { "Content-Type": "application/json", ...dashboardCsrfHeader() },
      body: JSON.stringify(body),
    })
    return res.ok
  } catch {
    return false
  }
}

export function NotificationHistoryPage({ initialHistory, initialErrorMessage = null }: Props) {
  const nowMs = useNowMs()
  const isClient = nowMs !== null
  const [history, setHistory] = useState(initialHistory)
  const [filters, setFilters] = useState<NotificationHistoryFilters>({
    status: "all",
    severity: "",
    module: "",
    q: "",
    page: initialHistory.page,
    pageSize: initialHistory.pageSize,
  })
  const [searchText, setSearchText] = useState("")
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(initialErrorMessage)
  // Only the newest request may write state, so a slow earlier filter never overwrites a later one.
  const requestId = useRef(0)

  async function load(next: NotificationHistoryFilters) {
    const id = ++requestId.current
    setFilters(next)
    setLoading(true)
    try {
      const res = await fetch(`${FRONTEND_ROUTES.notifications.history}?${historyQueryString(next)}`, {
        cache: "no-store",
        credentials: "same-origin",
      })
      const json = await res.json().catch(() => null)
      if (id !== requestId.current) return
      if (!res.ok || !json || json.status === "error") {
        setError(json?.message || "Could not load notifications.")
        return
      }
      setError(null)
      setHistory(json.data as NotificationHistory)
    } catch {
      if (id === requestId.current) setError("Could not reach the server.")
    } finally {
      if (id === requestId.current) setLoading(false)
    }
  }

  // Any filter change goes back to page 1; only the pager passes `page` explicitly.
  const apply = (patch: Partial<NotificationHistoryFilters>) => void load({ ...filters, page: 1, ...patch })

  function clearFilters() {
    setSearchText("")
    void load({ ...filters, status: "all", severity: "", module: "", q: "", page: 1 })
  }

  async function markRead(item: BackendNotification) {
    if (item.isRead || item.isResolved) return
    setHistory((h) => ({
      ...h,
      unread: Math.max(0, h.unread - 1),
      summary: { ...h.summary, unread: Math.max(0, h.summary.unread - 1) },
      items: h.items.map((n) => (n.id === item.id ? { ...n, isRead: true } : n)),
    }))
    const ok = await postJson(FRONTEND_ROUTES.notifications.read, { id: item.id })
    if (!ok) void load(filters)
    announceNotificationsChanged()
  }

  async function markAllRead() {
    const ok = await postJson(FRONTEND_ROUTES.notifications.readAll)
    if (!ok) setError("Could not mark notifications as read.")
    await load(filters)
    announceNotificationsChanged()
  }

  const { summary } = history
  const filtered = hasActiveFilters(filters)
  const first = history.total === 0 ? 0 : (history.page - 1) * history.pageSize + 1
  const last = Math.min(history.total, history.page * history.pageSize)

  // Group in the viewer's timezone once mounted; before that, in UTC with no "Today", to match the server render.
  const timeZone = isClient ? undefined : "UTC"
  const groups = groupAlertsByDay(history.items, { timeZone })

  return (
    <ListPageCard
      icon={Bell}
      title="Notifications"
      description="Every alert for the XML API, Deepdex and cron modules, including cleared ones. History is kept for a year."
      breadcrumb={[{ label: "Notifications" }, { label: "All" }]}
      actions={
        <div className="flex items-center gap-2">
          <Button type="button" variant="outline" size="sm" disabled={summary.unread === 0} onClick={() => void markAllRead()}>
            <CheckCheck className="mr-1.5 size-3.5" />
            Mark all read
          </Button>
          <Button asChild variant="outline" size="sm">
            <Link href={FRONTEND_ROUTES.notifications.settings}>
              <Settings className="mr-1.5 size-3.5" />
              Settings
            </Link>
          </Button>
        </div>
      }
    >
      <ErrorBanner message={error} className="mb-4" />

      {/* Filters */}
      <div className="mb-3 flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
        <div className="flex flex-wrap items-center gap-2">
          <Tabs value={filters.status} onValueChange={(v) => apply({ status: v as NotificationHistoryFilters["status"] })}>
            <TabsList className="h-8">
              <TabsTrigger value="all" className="px-2.5 text-xs">
                All
                <Count n={summary.active + summary.resolved} />
              </TabsTrigger>
              <TabsTrigger value="active" className="px-2.5 text-xs">
                Active
                <Count n={summary.active} />
              </TabsTrigger>
              <TabsTrigger value="unread" className="px-2.5 text-xs">
                Unread
                <Count n={summary.unread} />
              </TabsTrigger>
              <TabsTrigger value="resolved" className="px-2.5 text-xs">
                Resolved
                <Count n={summary.resolved} />
              </TabsTrigger>
            </TabsList>
          </Tabs>
          {summary.criticalActive > 0 ? (
            <button
              type="button"
              aria-pressed={filters.status === "active" && filters.severity === "critical"}
              onClick={() => apply({ status: "active", severity: "critical" })}
              className="inline-flex h-8 items-center gap-1.5 rounded-full border border-destructive/30 bg-destructive/10 px-3 font-medium text-destructive text-xs outline-none hover:bg-destructive/15 focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-pressed:ring-2 aria-pressed:ring-destructive/40"
            >
              <OctagonAlert className="size-3.5" />
              {summary.criticalActive} critical
            </button>
          ) : null}
        </div>

        <div className="flex flex-col gap-2 sm:flex-row sm:items-center">
          <form
            className="relative sm:w-52"
            onSubmit={(e) => {
              e.preventDefault()
              apply({ q: searchText.trim() })
            }}
          >
            <Search className="-translate-y-1/2 pointer-events-none absolute top-1/2 left-2.5 size-3.5 text-muted-foreground" />
            <Input
              value={searchText}
              onChange={(e) => setSearchText(e.target.value)}
              placeholder="Search alerts…"
              aria-label="Search notifications"
              className="h-8 pl-8 text-sm"
            />
          </form>
          <Select
            value={filters.severity || ANY}
            onValueChange={(v) => apply({ severity: v === ANY ? "" : (v as NotificationSeverity) })}
          >
            <SelectTrigger aria-label="Severity" size="sm" className="sm:w-32">
              <SelectValue placeholder="Severity" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value={ANY}>Any severity</SelectItem>
              <SelectItem value="critical">Critical</SelectItem>
              <SelectItem value="warning">Warning</SelectItem>
              <SelectItem value="info">Info</SelectItem>
            </SelectContent>
          </Select>
          <Select value={filters.module || ANY} onValueChange={(v) => apply({ module: v === ANY ? "" : v })}>
            <SelectTrigger aria-label="Module" size="sm" className="sm:w-48">
              <SelectValue placeholder="Module" />
            </SelectTrigger>
            <SelectContent className="max-h-72">
              <SelectItem value={ANY}>Any module</SelectItem>
              {history.modules.map((m) => (
                <SelectItem key={m} value={m}>
                  {m}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
          {filtered ? (
            <Button type="button" variant="ghost" size="sm" className="h-8" onClick={clearFilters}>
              <X className="mr-1 size-3.5" />
              Clear
            </Button>
          ) : null}
        </div>
      </div>

      {/* Timeline */}
      <div aria-busy={loading} className={cn("space-y-3 transition-opacity", loading && "opacity-60")}>
        {groups.length === 0 ? (
          <Card className="shadow-none">
            <CardContent>
              <Empty>
                <EmptyHeader>
                  <EmptyMedia variant="icon">
                    <BellOff />
                  </EmptyMedia>
                  <EmptyTitle>{filtered ? "No notifications match" : "Nothing to report"}</EmptyTitle>
                  <EmptyDescription>
                    {filtered
                      ? "Try a different status, severity or search, or clear the filters."
                      : "Alerts show up here when something in the XML API, Deepdex or cron jobs needs attention."}
                  </EmptyDescription>
                </EmptyHeader>
                {filtered ? (
                  <Button type="button" variant="outline" size="sm" onClick={clearFilters}>
                    Clear filters
                  </Button>
                ) : null}
              </Empty>
            </CardContent>
          </Card>
        ) : (
          groups.map((group) => (
            <section key={group.key} aria-label={alertDayLabel(group.key, nowMs, { timeZone })}>
              <div className="mb-1 flex items-center gap-2 px-1">
                <h2 className="font-medium text-[11px] text-muted-foreground uppercase tracking-wide">
                  {group.key === "unknown" ? "Undated" : alertDayLabel(group.key, nowMs, { timeZone })}
                </h2>
                <span className="text-[11px] text-muted-foreground/70 tabular-nums">{group.items.length}</span>
              </div>
              <Card className="gap-0 py-0 shadow-none">
                <ul className="divide-y">
                  {group.items.map((item) => (
                    <AlertRow key={item.id} item={item} nowMs={nowMs} timeZone={timeZone} onOpen={() => void markRead(item)} />
                  ))}
                </ul>
              </Card>
            </section>
          ))
        )}
      </div>

      {/* Footer */}
      <div className="mt-3 flex flex-wrap items-center justify-between gap-3">
        <p className="text-muted-foreground text-xs">
          {history.total === 0 ? "0 notifications" : `Showing ${first}–${last} of ${history.total}`}
        </p>
        <div className="flex flex-wrap items-center gap-4">
          <PageSizeSelect
            id="notification-history-page-size"
            value={filters.pageSize}
            options={PAGE_SIZES}
            onValueChange={(size) => apply({ pageSize: size })}
          />
          <PageStepButtons
            canPrevious={history.page > 1 && !loading}
            canNext={history.page < history.pageCount && !loading}
            onFirst={() => void load({ ...filters, page: 1 })}
            onPrevious={() => void load({ ...filters, page: history.page - 1 })}
            onNext={() => void load({ ...filters, page: history.page + 1 })}
            onLast={() => void load({ ...filters, page: history.pageCount })}
          >
            <span className="min-w-[100px] text-center text-sm">
              Page {history.page} of {history.pageCount}
            </span>
          </PageStepButtons>
        </div>
      </div>
    </ListPageCard>
  )
}

function Count({ n }: { n: number }) {
  return <span className="text-muted-foreground text-xs tabular-nums">{n}</span>
}

function AlertRow({
  item,
  nowMs,
  timeZone,
  onOpen,
}: {
  item: BackendNotification
  nowMs: number | null
  timeZone: string | undefined
  onOpen: () => void
}) {
  const [expanded, setExpanded] = useState(false)
  const isClient = nowMs !== null
  const sev = SEVERITY[item.severity]
  const Icon = item.isResolved ? CircleCheck : sev.icon
  const unread = !item.isRead && !item.isResolved
  const collapsible = item.body.includes("\n") || item.body.length > BODY_COLLAPSE_CHARS
  const lasted = alertLastedFor(item.raisedAt, item.resolvedAt)
  // Hover text and "5m ago" depend on the viewer's clock, so they wait for the client.
  const raisedTitle = isClient ? alertFullDateTime(item.raisedAt) : undefined
  const resolvedTitle = isClient ? alertFullDateTime(item.resolvedAt) : undefined

  return (
    <li className={cn("flex gap-2.5 px-3 py-2", unread && "bg-primary/[0.03]", item.isResolved && "bg-muted/20")}>
      <div className="relative mt-0.5 shrink-0">
        <div
          className={cn(
            "flex size-7 items-center justify-center rounded-md",
            item.isResolved ? "bg-emerald-500/10 text-emerald-600" : sev.chip
          )}
        >
          <Icon className="size-3.5" aria-label={item.isResolved ? "Resolved" : sev.label} />
        </div>
        {unread ? (
          <span aria-label="Unread" className="absolute -top-0.5 -right-0.5 size-2 rounded-full bg-destructive ring-2 ring-card" />
        ) : null}
      </div>

      <div className="min-w-0 flex-1">
        <div className="flex flex-wrap items-center gap-x-2 gap-y-0.5">
          <p className={cn("text-sm leading-5", unread ? "font-semibold" : "font-medium", item.isResolved && "text-muted-foreground")}>
            {item.title}
          </p>
          <Badge variant="outline" className={cn("h-4 px-1.5 font-normal text-[11px]", sev.badge)}>
            {sev.label}
          </Badge>
          {item.isResolved ? (
            <Badge variant="secondary" className="h-4 px-1.5 font-normal text-[11px]">
              Resolved
            </Badge>
          ) : null}
        </div>

        <p className="flex flex-wrap items-center gap-x-1.5 text-[11px] text-muted-foreground leading-4">
          <span>{item.module}</span>
          <span aria-hidden>·</span>
          <span title={raisedTitle}>
            Raised {alertClockTime(item.raisedAt, { timeZone })}
            {isClient ? ` (${formatRelativeTime(item.raisedAt, nowMs)})` : ""}
          </span>
          {item.isResolved ? (
            <>
              <span aria-hidden>·</span>
              <span title={resolvedTitle}>
                Cleared {alertClockTime(item.resolvedAt, { timeZone })}
                {lasted ? `, active for ${lasted}` : ""}
              </span>
            </>
          ) : null}
        </p>

        {item.body ? (
          <>
            <p
              className={cn(
                "mt-0.5 whitespace-pre-line text-muted-foreground text-xs leading-4",
                collapsible && !expanded && "line-clamp-1"
              )}
            >
              {item.body}
            </p>
            {collapsible ? (
              <button type="button" onClick={() => setExpanded((v) => !v)} className="text-primary text-[11px] hover:underline">
                {expanded ? "Show less" : "Show more"}
              </button>
            ) : null}
          </>
        ) : null}
      </div>

      {item.link ? (
        <Button asChild variant="ghost" size="sm" className="h-7 shrink-0 self-start px-2 text-muted-foreground text-xs hover:text-foreground">
          <Link href={item.link} onClick={onOpen}>
            Open
            <ArrowUpRight className="ml-0.5 size-3" />
          </Link>
        </Button>
      ) : null}
    </li>
  )
}
