import { useCallback, useEffect, useMemo, useState } from "react"
import {
  CheckCircle2,
  Megaphone,
  Plus,
  RefreshCw,
  Trash2,
} from "lucide-react"

import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-core-client"
import {
  ActionsCell,
  BadgeCell,
  DataTable,
  DateCell,
  Pagination,
  useTable,
  type ActionItem,
  type BadgeTone,
  type Column,
} from "@crema/table-ui"
import { SearchInput } from "@crema/search-ui"
import { AlertBanner, ConfirmDialog, EmptyState, LoadingOverlay } from "@crema/feedback-ui"

import { AppShell } from "~/components/layout/app-shell"
import { Badge } from "~/components/ui/badge"
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 { Textarea } from "~/components/ui/textarea"
import {
  createAnnouncement,
  deleteAnnouncement,
  listAnnouncements,
  updateAnnouncement,
  type Announcement,
  type AnnouncementInput,
  type AnnouncementType,
} from "~/lib/arcadia/announcements"
import { listTenants, type Tenant } from "~/lib/arcadia/tenants"
import { pageTitle } from "~/lib/page-meta"
import { useSession } from "~/lib/session"
import { useRegisterContext } from "@crema/aifirst-ui/context"

export const meta = () => pageTitle("Announcements")

const TYPES: AnnouncementType[] = ["info", "warning", "maintenance", "incident", "feature"]

const KIND_OPTIONS: { value: AnnouncementType; hint: string }[] = [
  { value: "info", hint: "Neutral update" },
  { value: "warning", hint: "Degraded service or heads-up" },
  { value: "maintenance", hint: "Scheduled work" },
  { value: "incident", hint: "Active outage" },
  { value: "feature", hint: "Something new shipped" },
]

function typeToAlertVariant(
  t: AnnouncementType,
): "info" | "success" | "warning" | "error" | "neutral" {
  if (t === "incident") return "error"
  if (t === "warning" || t === "maintenance") return "warning"
  if (t === "feature") return "success"
  return "info"
}

function publishButtonLabel(opts: {
  isEdit: boolean
  active: boolean
  audience: "platform" | "tenant"
  tenantId: string
  tenants: Tenant[]
}): string {
  if (opts.isEdit) return "Save changes"
  if (!opts.active) return "Save draft"
  if (opts.audience === "tenant") {
    const name = opts.tenants.find((t) => t.id === opts.tenantId)?.name
    return name ? `Publish to ${name}` : "Publish to tenant"
  }
  return "Publish to all users"
}

type Editor =
  | { kind: "create" }
  | { kind: "edit"; announcement: Announcement }
  | null

export default function AnnouncementsRoute() {
  const session = useSession()
  const arcadia = useArcadiaClient()

  const [items, setItems] = useState<Announcement[]>([])
  const [tenants, setTenants] = useState<Tenant[]>([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<string | null>(null)
  const [info, setInfo] = useState<string | null>(null)
  const [search, setSearch] = useState("")
  const [editor, setEditor] = useState<Editor>(null)
  const [pendingDelete, setPendingDelete] = useState<Announcement | null>(null)
  const [refreshedAt, setRefreshedAt] = useState<number | null>(null)
  const [now, setNow] = useState(() => Date.now())

  const refresh = useCallback(async () => {
    setError(null)
    setLoading(true)
    try {
      const [a, t] = await Promise.all([
        listAnnouncements(arcadia),
        listTenants(arcadia).catch(() => [] as Tenant[]),
      ])
      setItems(a)
      setTenants(t)
      setRefreshedAt(Date.now())
    } catch (err) {
      setError(err instanceof ArcadiaError ? err.message : "Failed to load announcements.")
    } finally {
      setLoading(false)
    }
  }, [arcadia])

  useEffect(() => {
    if (refreshedAt == null) return
    const id = window.setInterval(() => setNow(Date.now()), 30_000)
    return () => window.clearInterval(id)
  }, [refreshedAt])

  const lastRefreshedLabel = useMemo(() => {
    if (refreshedAt == null) return null
    const seconds = Math.max(1, Math.round((now - refreshedAt) / 1000))
    if (seconds < 60) return `${seconds}s ago`
    const minutes = Math.round(seconds / 60)
    if (minutes < 60) return `${minutes}m ago`
    return `${Math.round(minutes / 60)}h ago`
  }, [refreshedAt, now])

  useEffect(() => {
    if (session) refresh()
  }, [session, refresh])

  const columns = useMemo<Column<Announcement>[]>(
    () => [
      {
        id: "title",
        header: "Title",
        accessor: "title",
        sortable: true,
        cell: (a) => (
          <div className="flex flex-col">
            <span className="font-medium">{a.title}</span>
            {a.body ? (
              <span className="line-clamp-1 text-xs text-muted-foreground">{a.body}</span>
            ) : null}
          </div>
        ),
      },
      {
        id: "type",
        header: "Type",
        accessor: "announcement_type",
        sortable: true,
        cell: (a) => <BadgeCell label={a.announcement_type} tone={typeTone(a.announcement_type)} />,
      },
      {
        id: "scope",
        header: "Audience",
        cell: (a) => {
          if (!a.tenant_id) return <Badge>All apps</Badge>
          const t = tenants.find((x) => x.id === a.tenant_id)
          return <Badge variant="secondary">{t?.slug ?? "Single tenant"}</Badge>
        },
      },
      {
        id: "active",
        header: "Active",
        accessor: "active",
        sortable: true,
        cell: (a) => (
          <BadgeCell label={a.active ? "live" : "off"} tone={a.active ? "success" : "default"} />
        ),
      },
      {
        id: "window",
        header: "Window",
        cell: (a) => (
          <span className="text-xs text-muted-foreground">
            {a.starts_at ? new Date(a.starts_at).toLocaleDateString() : "—"}
            {" → "}
            {a.ends_at ? new Date(a.ends_at).toLocaleDateString() : "∞"}
          </span>
        ),
      },
      {
        id: "updated",
        header: "Updated",
        accessor: "updated_at",
        sortable: true,
        cell: (a) => <DateCell value={a.updated_at} format="short" />,
      },
      {
        id: "actions",
        header: "",
        align: "right",
        cell: (a) => {
          const items: ActionItem[] = [
            {
              id: "edit",
              label: "Edit",
              dataAction: `announcement-${a.id}-edit`,
              onSelect: () => setEditor({ kind: "edit", announcement: a }),
            },
            {
              id: "toggle",
              label: a.active ? "Deactivate" : "Activate",
              dataAction: `announcement-${a.id}-toggle`,
              onSelect: async () => {
                try {
                  await updateAnnouncement(arcadia, a.id, { active: !a.active })
                  setInfo(a.active ? "Announcement deactivated." : "Announcement activated.")
                  await refresh()
                } catch (err) {
                  setError(err instanceof ArcadiaError ? err.message : "Toggle failed.")
                }
              },
            },
            {
              id: "delete",
              label: "Delete",
              icon: <Trash2 className="size-4" />,
              destructive: true,
              dataAction: `announcement-${a.id}-delete`,
              onSelect: () => setPendingDelete(a),
            },
          ]
          return <ActionsCell items={items} triggerDataAction={`announcement-${a.id}-actions`} />
        },
      },
    ],
    [arcadia, refresh, tenants],
  )

  const summary = useMemo(
    () => ({
      total: items.length,
      active: items.filter((a) => a.active).length,
      byType: countBy(items, (a) => a.announcement_type),
    }),
    [items],
  )
  useRegisterContext("announcements", summary)

  const table = useTable<Announcement>({
    data: items,
    columns,
    getRowId: (a) => a.id,
    initialPageSize: 25,
    initialSearch: search,
  })
  useEffect(() => {
    table.setSearch(search)
  }, [search, table])

  return (
    <AppShell>
      <div className="flex flex-col gap-4">
        <header className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
          <div className="min-w-0">
            <h1 className="text-[26px] font-[620] leading-[1.1] tracking-[-0.02em]">
              Announcements
            </h1>
            <p className="mt-1.5 max-w-[56ch] text-[13.5px] leading-[1.5] text-muted-foreground">
              Banners that appear at the top of every Sky AI app. Use them for maintenance
              windows, incidents, or new features.
            </p>
          </div>
          <div className="flex shrink-0 items-center gap-3">
            {lastRefreshedLabel ? (
              <span
                className="text-xs tabular-nums text-muted-foreground"
                aria-live="polite"
                title={`Last refreshed ${lastRefreshedLabel}`}
              >
                <span className="hidden sm:inline">Updated </span>
                {lastRefreshedLabel}
              </span>
            ) : null}
            <Button
              variant="ghost"
              size="icon-sm"
              onClick={refresh}
              disabled={loading}
              aria-label="Refresh announcements"
              data-action="announcements-refresh"
              className="text-muted-foreground hover:text-foreground"
            >
              <RefreshCw className={`size-4 ${loading ? "animate-spin" : ""}`} />
            </Button>
            {items.length > 0 ? (
              <Button
                size="sm"
                onClick={() => setEditor({ kind: "create" })}
                data-action="announcements-create"
              >
                <Plus className="size-4" />
                New announcement
              </Button>
            ) : null}
          </div>
        </header>

        {error ? (
          <AlertBanner variant="error" dismissible onDismiss={() => setError(null)}>
            {error}
          </AlertBanner>
        ) : null}
        {info ? (
          <AlertBanner variant="success" dismissible onDismiss={() => setInfo(null)}>
            {info}
          </AlertBanner>
        ) : null}

        <Card>
          <CardHeader className="flex flex-row items-center gap-3">
            <SearchInput
              value={search}
              onValueChange={setSearch}
              placeholder="Search by title, body, or type"
              data-action="announcements-search"
              className="max-w-sm flex-1"
            />
            {items.length > 0 ? (
              <div className="ml-auto text-xs tabular-nums text-muted-foreground">
                {search && table.total !== items.length
                  ? `${table.total} of ${items.length}`
                  : `${items.length} ${items.length === 1 ? "announcement" : "announcements"}`}
              </div>
            ) : null}
          </CardHeader>

          <CardContent className="relative p-0">
            <LoadingOverlay
              active={loading && items.length === 0}
              label="Loading announcements…"
            />
            {table.total === 0 && !loading ? (
              <EmptyState
                icon={
                  <div
                    className="grid size-14 place-items-center rounded-full"
                    style={{
                      background:
                        "radial-gradient(circle at center, color-mix(in oklch, var(--primary) 22%, transparent), transparent 70%)",
                    }}
                  >
                    <Megaphone
                      className="size-6"
                      style={{ color: "var(--primary)" }}
                    />
                  </div>
                }
                title={search ? "No announcements match." : "No announcements yet."}
                description={
                  search
                    ? "Try a different search."
                    : "Post your first banner. Show it to everyone, or scope it to a single tenant."
                }
                action={
                  search ? (
                    <Button
                      size="sm"
                      variant="outline"
                      onClick={() => setSearch("")}
                      data-action="announcements-clear-search"
                    >
                      Clear search
                    </Button>
                  ) : (
                    <Button
                      size="sm"
                      onClick={() => setEditor({ kind: "create" })}
                      data-action="announcements-create-empty"
                    >
                      <Plus className="size-4" />
                      New announcement
                    </Button>
                  )
                }
              />
            ) : (
              <>
                <DataTable
                  columns={columns}
                  rows={table.pageRows}
                  getRowId={(a) => a.id}
                  sort={table.sort}
                  onSortToggle={table.toggleSort}
                  loading={loading && items.length > 0}
                  stickyHeader
                />
                <Pagination
                  page={table.page}
                  pageSize={table.pageSize}
                  total={table.total}
                  onPageChange={table.setPage}
                  onPageSizeChange={table.setPageSize}
                />
              </>
            )}
          </CardContent>
        </Card>
      </div>

      <ConfirmDialog
        open={pendingDelete !== null}
        onOpenChange={(o) => !o && setPendingDelete(null)}
        title="Delete announcement?"
        description={pendingDelete ? `${pendingDelete.title} will be removed for all users.` : ""}
        confirmLabel="Delete"
        variant="danger"
        onConfirm={async () => {
          if (!pendingDelete) return
          try {
            await deleteAnnouncement(arcadia, pendingDelete.id)
            setPendingDelete(null)
            setInfo("Announcement deleted.")
            await refresh()
          } catch (err) {
            setError(err instanceof ArcadiaError ? err.message : "Delete failed.")
            setPendingDelete(null)
          }
        }}
      />

      <AnnouncementEditorDialog
        state={editor}
        tenants={tenants}
        onClose={() => setEditor(null)}
        onSaved={async (msg) => {
          setEditor(null)
          if (msg) setInfo(msg)
          await refresh()
        }}
        onError={setError}
      />
    </AppShell>
  )
}

function typeTone(t: AnnouncementType): BadgeTone {
  if (t === "incident") return "danger"
  if (t === "warning" || t === "maintenance") return "warning"
  if (t === "feature") return "success"
  return "default"
}

function countBy<T>(arr: T[], key: (x: T) => string): Record<string, number> {
  return arr.reduce<Record<string, number>>((acc, x) => {
    const k = key(x)
    acc[k] = (acc[k] ?? 0) + 1
    return acc
  }, {})
}

function AnnouncementEditorDialog({
  state,
  tenants,
  onClose,
  onSaved,
  onError,
}: {
  state: Editor
  tenants: Tenant[]
  onClose: () => void
  onSaved: (msg?: string) => Promise<void>
  onError: (msg: string | null) => void
}) {
  const arcadia = useArcadiaClient()
  const open = state !== null
  const isEdit = state?.kind === "edit"
  const initial = isEdit ? state.announcement : null

  const [title, setTitle] = useState("")
  const [body, setBody] = useState("")
  const [type, setType] = useState<AnnouncementType>("info")
  const [audience, setAudience] = useState<"platform" | "tenant">("platform")
  const [tenantId, setTenantId] = useState<string>("")
  const [actionLabel, setActionLabel] = useState("")
  const [actionUrl, setActionUrl] = useState("")
  const [startsAt, setStartsAt] = useState("")
  const [endsAt, setEndsAt] = useState("")
  const [dismissible, setDismissible] = useState(true)
  const [active, setActive] = useState(true)
  const [saving, setSaving] = useState(false)
  const [localError, setLocalError] = useState<string | null>(null)

  useEffect(() => {
    if (!open) setLocalError(null)
  }, [open])

  useEffect(() => {
    if (!open) return
    if (initial) {
      setTitle(initial.title)
      setBody(initial.body ?? "")
      setType(initial.announcement_type)
      setAudience(initial.tenant_id ? "tenant" : "platform")
      setTenantId(initial.tenant_id ?? "")
      setActionLabel(initial.action_label ?? "")
      setActionUrl(initial.action_url ?? "")
      setStartsAt(initial.starts_at ? initial.starts_at.slice(0, 16) : "")
      setEndsAt(initial.ends_at ? initial.ends_at.slice(0, 16) : "")
      setDismissible(initial.dismissible)
      setActive(initial.active)
    } else {
      setTitle("")
      setBody("")
      setType("info")
      setAudience("platform")
      setTenantId("")
      setActionLabel("")
      setActionUrl("")
      setStartsAt("")
      setEndsAt("")
      setDismissible(true)
      setActive(true)
    }
  }, [open, initial])

  const submit = async () => {
    onError(null)
    setLocalError(null)
    setSaving(true)
    try {
      const input: AnnouncementInput = {
        title,
        body: body || undefined,
        announcement_type: type,
        audience,
        action_label: actionLabel || null,
        action_url: actionUrl || null,
        starts_at: startsAt ? new Date(startsAt).toISOString() : null,
        ends_at: endsAt ? new Date(endsAt).toISOString() : null,
        dismissible,
        active,
        tenant_id: audience === "tenant" ? tenantId || null : null,
      }
      if (isEdit && initial) {
        await updateAnnouncement(arcadia, initial.id, input)
        await onSaved("Announcement updated.")
      } else {
        await createAnnouncement(arcadia, input)
        await onSaved("Announcement posted.")
      }
    } catch (err) {
      const msg =
        err instanceof ArcadiaError
          ? err.message
          : err instanceof Error
            ? err.message
            : "Save failed."
      setLocalError(msg)
    } finally {
      setSaving(false)
    }
  }

  return (
    <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>{isEdit ? "Edit announcement" : "New announcement"}</DialogTitle>
          <DialogDescription>
            A banner shows at the top of every Sky AI app. It's visible when it's switched on
            and today falls inside its date range.
          </DialogDescription>
        </DialogHeader>

        {/* Live preview — what users will see. Updates as the form is edited so
            the operator never has to imagine the output or publish blind. */}
        <div className="flex flex-col gap-1.5">
          <Label className="text-xs uppercase tracking-wider text-muted-foreground">
            Preview
          </Label>
          <div className="rounded-md border bg-muted/30 p-3">
            <AlertBanner
              variant={typeToAlertVariant(type)}
              title={title || "Your banner title appears here"}
              dismissible={dismissible}
              onDismiss={() => {}}
              action={
                actionLabel && actionUrl ? (
                  <Button size="xs" variant="outline" type="button" tabIndex={-1}>
                    {actionLabel}
                  </Button>
                ) : undefined
              }
            >
              {body || (
                <span className="italic opacity-60">Body text appears here.</span>
              )}
            </AlertBanner>
            <p className="mt-2 text-[11px] text-muted-foreground">
              {audience === "tenant"
                ? `Visible to users of ${
                    tenants.find((t) => t.id === tenantId)?.name ?? "the selected tenant"
                  } only.`
                : "Visible to everyone across every Sky AI app."}
            </p>
          </div>
        </div>

        {localError ? (
          <AlertBanner
            variant="error"
            dismissible
            onDismiss={() => setLocalError(null)}
          >
            {localError}
          </AlertBanner>
        ) : null}

        <div className="grid grid-cols-2 gap-3">
          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="ann-title">Title</Label>
            <Input
              id="ann-title"
              value={title}
              onChange={(e) => setTitle(e.target.value)}
              data-action="announcement-form-title"
              placeholder="Scheduled maintenance Sunday 2am AEST"
            />
          </div>
          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="ann-body">Body</Label>
            <Textarea
              id="ann-body"
              value={body}
              onChange={(e) => setBody(e.target.value)}
              rows={3}
              data-action="announcement-form-body"
              placeholder="Expect ~10 minutes of downtime while we ship the new tenant switcher."
            />
          </div>

          <div className="flex flex-col gap-1.5">
            <Label>Kind</Label>
            <Select value={type} onValueChange={setType}>
              <SelectTrigger data-action="announcement-form-type">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {KIND_OPTIONS.map((opt) => (
                  <SelectItem key={opt.value} value={opt.value}>
                    <div className="flex flex-col">
                      <span className="font-medium capitalize">{opt.value}</span>
                      <span className="text-xs text-muted-foreground">{opt.hint}</span>
                    </div>
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          <div className="flex flex-col gap-1.5">
            <Label>Who sees this</Label>
            <Select value={audience} onValueChange={(v) => setAudience(v as "platform" | "tenant")}>
              <SelectTrigger data-action="announcement-form-audience">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="platform">Everyone</SelectItem>
                <SelectItem value="tenant">Just one tenant</SelectItem>
              </SelectContent>
            </Select>
          </div>

          {audience === "tenant" ? (
            <div className="col-span-2 flex flex-col gap-1.5">
              <Label>Which tenant</Label>
              <Select value={tenantId} onValueChange={setTenantId}>
                <SelectTrigger data-action="announcement-form-tenant">
                  <SelectValue placeholder="Pick a tenant" />
                </SelectTrigger>
                <SelectContent>
                  {tenants.map((t) => (
                    <SelectItem key={t.id} value={t.id}>
                      {t.name} ({t.slug})
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          ) : null}

          <div className="flex flex-col gap-1.5">
            <Label htmlFor="ann-starts">Starts</Label>
            <Input
              id="ann-starts"
              type="datetime-local"
              value={startsAt}
              onChange={(e) => setStartsAt(e.target.value)}
              data-action="announcement-form-starts"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="ann-ends">Ends</Label>
            <Input
              id="ann-ends"
              type="datetime-local"
              value={endsAt}
              onChange={(e) => setEndsAt(e.target.value)}
              data-action="announcement-form-ends"
            />
          </div>

          {/* Optional link group — heading clarifies these two are paired. */}
          <div className="col-span-2 flex flex-col gap-2 rounded-md border border-dashed p-3">
            <div className="flex items-baseline justify-between gap-2">
              <Label className="text-sm">Add a link</Label>
              <span className="text-xs text-muted-foreground">Optional</span>
            </div>
            <div className="grid grid-cols-2 gap-3">
              <div className="flex flex-col gap-1.5">
                <Label htmlFor="ann-action-label" className="text-xs text-muted-foreground">
                  Button text
                </Label>
                <Input
                  id="ann-action-label"
                  value={actionLabel}
                  onChange={(e) => setActionLabel(e.target.value)}
                  placeholder="Read more"
                  data-action="announcement-form-action-label"
                />
              </div>
              <div className="flex flex-col gap-1.5">
                <Label htmlFor="ann-action-url" className="text-xs text-muted-foreground">
                  Where it goes
                </Label>
                <Input
                  id="ann-action-url"
                  value={actionUrl}
                  onChange={(e) => setActionUrl(e.target.value)}
                  placeholder="/changelog/v2"
                  data-action="announcement-form-action-url"
                />
              </div>
            </div>
          </div>

          {/* End-user behavior toggle, not publish state — kept with content fields. */}
          <div className="col-span-2 flex items-center justify-between rounded-md border px-3 py-2">
            <div className="flex flex-col">
              <Label className="text-sm">Let users dismiss</Label>
              <span className="text-xs text-muted-foreground">
                Adds an × users can click to hide the banner.
              </span>
            </div>
            <Switch
              checked={dismissible}
              onCheckedChange={setDismissible}
              data-action="announcement-form-dismissible"
            />
          </div>
        </div>

        <DialogFooter className="flex-col items-stretch gap-3 sm:flex-row sm:items-center sm:justify-between">
          {/* Active = publish state, paired with the publish button. */}
          <label
            htmlFor="ann-active"
            className="flex items-center gap-2 text-xs text-muted-foreground sm:mr-auto"
          >
            <Switch
              id="ann-active"
              checked={active}
              onCheckedChange={setActive}
              data-action="announcement-form-active"
            />
            <span>{active ? "Switched on" : "Switched off (draft)"}</span>
          </label>

          <div className="flex items-center justify-end gap-2">
            <Button variant="outline" onClick={onClose} disabled={saving}>
              Cancel
            </Button>
            <Button
              onClick={submit}
              disabled={saving || !title.trim() || (audience === "tenant" && !tenantId)}
              data-action="announcement-form-save"
            >
              {saving ? (
                <RefreshCw className="size-4 animate-spin" />
              ) : (
                <CheckCircle2 className="size-4" />
              )}
              {publishButtonLabel({ isEdit, active, audience, tenantId, tenants })}
            </Button>
          </div>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}
