import { useCallback, useEffect, useMemo, useState } from "react"
import { Link } from "react-router"
import {
  CalendarClock,
  CheckCircle2,
  Clock,
  History,
  Pause,
  Play,
  Plus,
  RefreshCw,
  Trash2,
  Zap,
} from "lucide-react"

import { ArcadiaError, useArcadiaClient } from "@crema/arcadia-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 {
  createScheduledTask,
  deleteScheduledTask,
  disableScheduledTask,
  enableScheduledTask,
  listScheduledTasks,
  listTaskRuns,
  triggerScheduledTask,
  updateScheduledTask,
  type ScheduledTask,
  type ScheduledTaskAction,
  type ScheduledTaskInput,
  type TaskRun,
} from "~/lib/arcadia/scheduled-tasks"
import { pageTitle } from "~/lib/page-meta"
import { useSession } from "~/lib/session"
import { useRegisterAdminContext } from "~/lib/admin-context"

export const meta = () => pageTitle("Scheduled tasks")

type EditorState =
  | { mode: "create" }
  | { mode: "edit"; task: ScheduledTask }
  | null

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

  const [tasks, setTasks] = useState<ScheduledTask[]>([])
  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<EditorState>(null)
  const [pendingDelete, setPendingDelete] = useState<ScheduledTask | null>(null)
  const [runsFor, setRunsFor] = useState<ScheduledTask | null>(null)

  const refresh = useCallback(async () => {
    setError(null)
    setLoading(true)
    try {
      setTasks(await listScheduledTasks(arcadia))
    } catch (err) {
      setError(err instanceof ArcadiaError ? err.message : "Failed to load scheduled tasks.")
    } finally {
      setLoading(false)
    }
  }, [arcadia])

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

  const columns = useMemo<Column<ScheduledTask>[]>(
    () => [
      {
        id: "name",
        header: "Name",
        accessor: "name",
        sortable: true,
        cell: (t) => (
          <div className="flex flex-col">
            <span className="font-medium">{t.name}</span>
            {t.description ? (
              <span className="text-xs text-muted-foreground">{t.description}</span>
            ) : null}
          </div>
        ),
      },
      {
        id: "cron",
        header: "Schedule",
        accessor: "cron_expression",
        sortable: true,
        cell: (t) => (
          <div className="flex flex-col">
            <code className="rounded bg-muted px-1.5 py-0.5 font-mono text-xs">
              {t.cron_expression}
            </code>
            <span className="text-[11px] text-muted-foreground">{t.timezone}</span>
          </div>
        ),
      },
      {
        id: "action",
        header: "Action",
        accessor: "action_type",
        sortable: true,
        cell: (t) => (
          <Badge variant="secondary" className="font-mono text-xs">
            {t.action_type}
          </Badge>
        ),
      },
      {
        id: "status",
        header: "Status",
        accessor: "enabled",
        sortable: true,
        cell: (t) => (
          <BadgeCell
            label={t.enabled ? "enabled" : "disabled"}
            tone={t.enabled ? "success" : "default"}
          />
        ),
      },
      {
        id: "last",
        header: "Last run",
        accessor: "last_run_at",
        sortable: true,
        cell: (t) =>
          t.last_run_at ? (
            <DateCell value={t.last_run_at} format="short" />
          ) : (
            <span className="text-muted-foreground">never</span>
          ),
      },
      {
        id: "next",
        header: "Next run",
        accessor: "next_run_at",
        sortable: true,
        cell: (t) =>
          t.enabled && t.next_run_at ? (
            <DateCell value={t.next_run_at} format="short" />
          ) : (
            <span className="text-muted-foreground">—</span>
          ),
      },
      {
        id: "actions",
        header: "",
        align: "right",
        cell: (t) => (
          <ActionsCell
            items={rowActions(t, {
              arcadia,
              refresh,
              setEditor,
              setPendingDelete,
              setRunsFor,
              setError,
              setInfo,
            })}
            triggerDataAction={`task-${t.id}-actions`}
          />
        ),
      },
    ],
    [arcadia, refresh],
  )

  const summary = useMemo(
    () => ({
      total: tasks.length,
      enabled: tasks.filter((t) => t.enabled).length,
      byAction: countBy(tasks, (t) => t.action_type),
      tasks: tasks.map((t) => ({
        name: t.name,
        cron: t.cron_expression,
        timezone: t.timezone,
        action_type: t.action_type,
        enabled: t.enabled,
        last_run_at: t.last_run_at,
        next_run_at: t.next_run_at,
      })),
    }),
    [tasks],
  )
  useRegisterAdminContext("scheduled_tasks", summary)

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

  if (!session) {
    return (
      <AppShell title="Scheduled tasks">
        <div className="p-8">
          <Card className="max-w-md">
            <CardHeader>
              <CardTitle>Sign in required</CardTitle>
              <CardDescription>
                Scheduled task administration requires an admin session.
              </CardDescription>
            </CardHeader>
            <CardContent>
              <Button asChild>
                <Link to="/login?next=/scheduled-tasks">Sign in</Link>
              </Button>
            </CardContent>
          </Card>
        </div>
      </AppShell>
    )
  }

  return (
    <AppShell title="Scheduled tasks">
      <div className="flex flex-col gap-4 p-6">
        <header className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
          <div>
            <h1 className="text-2xl font-semibold tracking-tight">Scheduled tasks</h1>
            <p className="text-sm text-muted-foreground">
              Cron-driven jobs run by arcadia. Trigger a task manually to test it without waiting
              for the next scheduled run.
            </p>
          </div>
          <div className="flex items-center gap-2">
            <Button
              variant="outline"
              size="sm"
              onClick={refresh}
              disabled={loading}
              data-action="tasks-refresh"
            >
              <RefreshCw className={`size-4 ${loading ? "animate-spin" : ""}`} />
              Refresh
            </Button>
            <Button
              size="sm"
              onClick={() => setEditor({ mode: "create" })}
              data-action="tasks-create"
            >
              <Plus className="size-4" />
              New task
            </Button>
          </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 name, cron, or action"
              data-action="tasks-search"
              className="max-w-sm flex-1"
            />
            <div className="ml-auto text-xs text-muted-foreground">
              {table.total} of {tasks.length}
            </div>
          </CardHeader>

          <CardContent className="relative p-0">
            <LoadingOverlay active={loading && tasks.length === 0} label="Loading tasks…" />
            {table.total === 0 && !loading ? (
              <EmptyState
                icon={<CalendarClock className="size-6" />}
                title={search ? "No tasks match." : "No scheduled tasks yet."}
                description={
                  search
                    ? "Try a different search."
                    : "Schedule a recurring webhook or platform event."
                }
                className="py-12"
              />
            ) : (
              <>
                <DataTable
                  columns={columns}
                  rows={table.pageRows}
                  getRowId={(t) => t.id}
                  sort={table.sort}
                  onSortToggle={table.toggleSort}
                  loading={loading && tasks.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 scheduled task?"
        description={
          pendingDelete
            ? `${pendingDelete.name} will be permanently removed. Run history is retained.`
            : ""
        }
        confirmLabel="Delete"
        variant="danger"
        onConfirm={async () => {
          if (!pendingDelete) return
          try {
            await deleteScheduledTask(arcadia, pendingDelete.id)
            setPendingDelete(null)
            setInfo("Task deleted.")
            await refresh()
          } catch (err) {
            setError(err instanceof ArcadiaError ? err.message : "Delete failed.")
            setPendingDelete(null)
          }
        }}
      />

      <TaskEditorDialog
        state={editor}
        onClose={() => setEditor(null)}
        onSaved={async () => {
          setEditor(null)
          await refresh()
        }}
        onError={setError}
      />

      <RunsDialog task={runsFor} onClose={() => setRunsFor(null)} onError={setError} />
    </AppShell>
  )
}

function rowActions(
  t: ScheduledTask,
  ctx: {
    arcadia: ReturnType<typeof useArcadiaClient>
    refresh: () => Promise<void>
    setEditor: (s: EditorState) => void
    setPendingDelete: (t: ScheduledTask | null) => void
    setRunsFor: (t: ScheduledTask | null) => void
    setError: (m: string | null) => void
    setInfo: (m: string | null) => void
  },
): ActionItem[] {
  const { arcadia, refresh, setEditor, setPendingDelete, setRunsFor, setError, setInfo } = ctx
  const items: ActionItem[] = []

  items.push({
    id: "trigger",
    label: "Run now",
    icon: <Zap className="size-4" />,
    dataAction: `task-${t.id}-trigger`,
    onSelect: async () => {
      try {
        await triggerScheduledTask(arcadia, t.id)
        setInfo(`${t.name} triggered. Check the run log for status.`)
        await refresh()
      } catch (err) {
        setError(err instanceof ArcadiaError ? err.message : "Trigger failed.")
      }
    },
  })
  items.push({
    id: "edit",
    label: "Edit",
    dataAction: `task-${t.id}-edit`,
    onSelect: () => setEditor({ mode: "edit", task: t }),
  })
  items.push({
    id: "runs",
    label: "View runs",
    icon: <History className="size-4" />,
    dataAction: `task-${t.id}-runs`,
    onSelect: () => setRunsFor(t),
  })

  if (t.enabled) {
    items.push({
      id: "disable",
      label: "Disable",
      icon: <Pause className="size-4" />,
      dataAction: `task-${t.id}-disable`,
      onSelect: async () => {
        try {
          await disableScheduledTask(arcadia, t.id)
          setInfo(`${t.name} disabled.`)
          await refresh()
        } catch (err) {
          setError(err instanceof ArcadiaError ? err.message : "Disable failed.")
        }
      },
    })
  } else {
    items.push({
      id: "enable",
      label: "Enable",
      icon: <Play className="size-4" />,
      dataAction: `task-${t.id}-enable`,
      onSelect: async () => {
        try {
          await enableScheduledTask(arcadia, t.id)
          setInfo(`${t.name} enabled.`)
          await refresh()
        } catch (err) {
          setError(err instanceof ArcadiaError ? err.message : "Enable failed.")
        }
      },
    })
  }

  items.push({
    id: "delete",
    label: "Delete",
    icon: <Trash2 className="size-4" />,
    destructive: true,
    dataAction: `task-${t.id}-delete`,
    onSelect: () => setPendingDelete(t),
  })

  return items
}

function TaskEditorDialog({
  state,
  onClose,
  onSaved,
  onError,
}: {
  state: EditorState
  onClose: () => void
  onSaved: () => Promise<void>
  onError: (msg: string | null) => void
}) {
  const arcadia = useArcadiaClient()
  const open = state !== null
  const isEdit = state?.mode === "edit"
  const initial = isEdit ? state.task : null

  const [name, setName] = useState("")
  const [description, setDescription] = useState("")
  const [cron, setCron] = useState("")
  const [timezone, setTimezone] = useState("UTC")
  const [actionType, setActionType] = useState<ScheduledTaskAction>("event")
  const [configText, setConfigText] = useState("{}")
  const [tagsText, setTagsText] = useState("")
  const [enabled, setEnabled] = useState(true)
  const [maxRetries, setMaxRetries] = useState("3")
  const [timeoutSeconds, setTimeoutSeconds] = useState("30")
  const [saving, setSaving] = useState(false)

  useEffect(() => {
    if (!open) return
    if (initial) {
      setName(initial.name)
      setDescription(initial.description ?? "")
      setCron(initial.cron_expression)
      setTimezone(initial.timezone)
      setActionType(initial.action_type)
      setConfigText(
        initial.action_config ? JSON.stringify(initial.action_config, null, 2) : "{}",
      )
      setTagsText(initial.tags.join(", "))
      setEnabled(initial.enabled)
      setMaxRetries(String(initial.max_retries))
      setTimeoutSeconds(String(initial.timeout_seconds))
    } else {
      setName("")
      setDescription("")
      setCron("0 * * * *")
      setTimezone("UTC")
      setActionType("event")
      setConfigText('{\n  "event": "platform.heartbeat"\n}')
      setTagsText("")
      setEnabled(true)
      setMaxRetries("3")
      setTimeoutSeconds("30")
    }
  }, [open, initial])

  const submit = async () => {
    onError(null)
    setSaving(true)
    try {
      let parsedConfig: Record<string, unknown>
      try {
        parsedConfig = configText.trim() === "" ? {} : JSON.parse(configText)
      } catch {
        throw new Error("Action config must be valid JSON.")
      }

      const tags = tagsText
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean)

      const input: ScheduledTaskInput = {
        name,
        description: description || null,
        cron_expression: cron,
        timezone,
        action_type: actionType,
        action_config: parsedConfig,
        tags,
        enabled,
        max_retries: Math.max(0, Number(maxRetries) || 0),
        timeout_seconds: Math.max(1, Number(timeoutSeconds) || 30),
      }

      if (isEdit && initial) await updateScheduledTask(arcadia, initial.id, input)
      else await createScheduledTask(arcadia, input)
      await onSaved()
    } catch (err) {
      onError(
        err instanceof ArcadiaError
          ? err.message
          : err instanceof Error
            ? err.message
            : "Save failed.",
      )
    } 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 scheduled task" : "New scheduled task"}</DialogTitle>
          <DialogDescription>
            Cron uses standard 5-field syntax (minute hour dom month dow). Tasks run in the
            specified timezone.
          </DialogDescription>
        </DialogHeader>

        <div className="grid grid-cols-2 gap-3">
          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="task-name">Name</Label>
            <Input
              id="task-name"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="Daily cleanup"
              data-action="task-form-name"
            />
          </div>
          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="task-description">Description</Label>
            <Input
              id="task-description"
              value={description}
              onChange={(e) => setDescription(e.target.value)}
              data-action="task-form-description"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="task-cron">Cron expression</Label>
            <Input
              id="task-cron"
              value={cron}
              onChange={(e) => setCron(e.target.value)}
              placeholder="0 2 * * *"
              data-action="task-form-cron"
              spellCheck={false}
              className="font-mono"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="task-timezone">Timezone</Label>
            <Input
              id="task-timezone"
              value={timezone}
              onChange={(e) => setTimezone(e.target.value)}
              placeholder="UTC"
              data-action="task-form-timezone"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label>Action type</Label>
            <Select
              value={actionType}
              onValueChange={(v) => setActionType(v as ScheduledTaskAction)}
            >
              <SelectTrigger data-action="task-form-action-type">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="event">Emit platform event</SelectItem>
                <SelectItem value="webhook">Send outbound webhook</SelectItem>
              </SelectContent>
            </Select>
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="task-tags">Tags (comma-separated)</Label>
            <Input
              id="task-tags"
              value={tagsText}
              onChange={(e) => setTagsText(e.target.value)}
              placeholder="cleanup, nightly"
              data-action="task-form-tags"
            />
          </div>
          <div className="col-span-2 flex flex-col gap-1.5">
            <Label htmlFor="task-config">
              Action config (JSON){" "}
              <span className="font-normal text-muted-foreground">
                {actionType === "webhook"
                  ? "{ url, method?, headers?, body? }"
                  : "{ event: 'name', payload?: {…} }"}
              </span>
            </Label>
            <Textarea
              id="task-config"
              rows={8}
              value={configText}
              onChange={(e) => setConfigText(e.target.value)}
              data-action="task-form-config"
              spellCheck={false}
              className="font-mono text-xs"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="task-retries">Max retries</Label>
            <Input
              id="task-retries"
              type="number"
              min={0}
              value={maxRetries}
              onChange={(e) => setMaxRetries(e.target.value)}
              data-action="task-form-retries"
            />
          </div>
          <div className="flex flex-col gap-1.5">
            <Label htmlFor="task-timeout">Timeout (seconds)</Label>
            <Input
              id="task-timeout"
              type="number"
              min={1}
              value={timeoutSeconds}
              onChange={(e) => setTimeoutSeconds(e.target.value)}
              data-action="task-form-timeout"
            />
          </div>
          <div className="col-span-2 flex items-center justify-between rounded-md border px-3 py-2">
            <div>
              <div className="text-sm font-medium">Enabled</div>
              <div className="text-xs text-muted-foreground">
                Disabled tasks skip their scheduled runs.
              </div>
            </div>
            <Switch
              checked={enabled}
              onCheckedChange={setEnabled}
              data-action="task-form-enabled"
            />
          </div>
        </div>

        <DialogFooter>
          <Button variant="outline" onClick={onClose} disabled={saving} data-action="task-form-cancel">
            Cancel
          </Button>
          <Button
            onClick={submit}
            disabled={saving || !name || !cron}
            data-action="task-form-save"
          >
            {saving ? <RefreshCw className="size-4 animate-spin" /> : <CheckCircle2 className="size-4" />}
            {isEdit ? "Save" : "Create"}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

function RunsDialog({
  task,
  onClose,
  onError,
}: {
  task: ScheduledTask | null
  onClose: () => void
  onError: (msg: string | null) => void
}) {
  const arcadia = useArcadiaClient()
  const [runs, setRuns] = useState<TaskRun[]>([])
  const [loading, setLoading] = useState(true)
  const [expanded, setExpanded] = useState<string | null>(null)

  useEffect(() => {
    if (!task) return
    let mounted = true
    setLoading(true)
    listTaskRuns(arcadia, task.id, { limit: 50 })
      .then((r) => mounted && setRuns(r))
      .catch((err) =>
        onError(err instanceof ArcadiaError ? err.message : "Failed to load runs."),
      )
      .finally(() => mounted && setLoading(false))
    return () => {
      mounted = false
    }
  }, [arcadia, task, onError])

  if (!task) return null

  return (
    <Dialog open onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="sm:max-w-3xl max-h-[85vh] overflow-y-auto">
        <DialogHeader>
          <DialogTitle>Run history</DialogTitle>
          <DialogDescription>
            {task.name} — last 50 runs, newest first.
          </DialogDescription>
        </DialogHeader>

        {loading ? (
          <p className="py-6 text-center text-sm text-muted-foreground">
            <RefreshCw className="mr-1 inline size-3.5 animate-spin" /> Loading…
          </p>
        ) : runs.length === 0 ? (
          <p className="py-6 text-center text-sm text-muted-foreground">No runs yet.</p>
        ) : (
          <ul className="flex flex-col divide-y rounded-md border">
            {runs.map((r) => {
              const open = expanded === r.id
              return (
                <li key={r.id} className="flex flex-col gap-1 px-3 py-2 text-sm">
                  <button
                    type="button"
                    onClick={() => setExpanded(open ? null : r.id)}
                    data-action={`task-run-${r.id}-toggle`}
                    className="flex items-start justify-between gap-3 text-left"
                  >
                    <div className="flex flex-col gap-0.5">
                      <span className="flex items-center gap-2">
                        <Badge variant={runVariant(r.status)}>{r.status}</Badge>
                        <span className="text-xs text-muted-foreground">
                          attempt {r.attempt}
                        </span>
                        {r.response_status ? (
                          <span className="text-xs text-muted-foreground">
                            HTTP {r.response_status}
                          </span>
                        ) : null}
                      </span>
                      <span className="text-xs text-muted-foreground">
                        <Clock className="mr-1 inline size-3" />
                        {r.started_at
                          ? `started ${new Date(r.started_at).toLocaleString()}`
                          : `queued ${new Date(r.inserted_at).toLocaleString()}`}
                        {r.finished_at
                          ? ` · finished ${new Date(r.finished_at).toLocaleString()}`
                          : ""}
                      </span>
                    </div>
                  </button>
                  {open ? (
                    <div className="ml-1 mt-1 flex flex-col gap-2">
                      {r.error ? (
                        <div>
                          <div className="text-xs font-semibold text-destructive">Error</div>
                          <pre className="overflow-x-auto rounded-md border bg-muted/50 p-2 text-xs">
                            {r.error}
                          </pre>
                        </div>
                      ) : null}
                      {r.response_body ? (
                        <div>
                          <div className="text-xs font-semibold">Response body</div>
                          <pre className="max-h-48 overflow-auto rounded-md border bg-muted/50 p-2 text-xs">
                            {r.response_body}
                          </pre>
                        </div>
                      ) : null}
                      {!r.error && !r.response_body ? (
                        <p className="text-xs text-muted-foreground">No response captured.</p>
                      ) : null}
                    </div>
                  ) : null}
                </li>
              )
            })}
          </ul>
        )}

        <DialogFooter>
          <Button variant="outline" onClick={onClose} data-action="task-runs-close">
            Close
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

function runVariant(status: string): "default" | "secondary" | "destructive" | "outline" {
  if (status === "succeeded") return "default"
  if (status === "failed") return "destructive"
  if (status === "running" || status === "pending") return "secondary"
  return "outline"
}

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
  }, {})
}
