"use client";

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";

type TaskType = "say" | "backup" | "snapshot-prune";

type Task = {
  id: string;
  type: TaskType;
  label: string;
  cron: string;
  enabled: boolean;
  params: { message?: string; keep?: number };
};

type Preset = "daily" | "every-hours" | "weekly";

const DOW_LABELS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

function cronFromForm(form: {
  preset: Preset;
  hour: number;
  minute: number;
  hours: number;
  dow: number;
}): string {
  const { preset, hour, minute, hours, dow } = form;
  if (preset === "daily") return `${minute} ${hour} * * *`;
  if (preset === "weekly") return `${minute} ${hour} * * ${dow}`;
  return `0 */${hours} * * *`;
}

function describeCron(cron: string): string {
  const parts = cron.trim().split(/\s+/);
  if (parts.length !== 5) return cron;
  const [m, h, , , dow] = parts;
  if (dow === "*" && h.startsWith("*/")) return `every ${h.slice(2)}h`;
  if (/^\d+$/.test(dow) && /^\d+$/.test(h) && /^\d+$/.test(m)) {
    return `${DOW_LABELS[parseInt(dow) % 7]} at ${h.padStart(2, "0")}:${m.padStart(2, "0")}`;
  }
  if (dow === "*" && /^\d+$/.test(h) && /^\d+$/.test(m)) {
    return `daily at ${h.padStart(2, "0")}:${m.padStart(2, "0")}`;
  }
  return cron;
}

function nextRun(cron: string): Date | null {
  const parts = cron.trim().split(/\s+/);
  if (parts.length !== 5) return null;
  const [m, h, , , dow] = parts;
  const now = new Date();

  if (/^\d+$/.test(dow) && /^\d+$/.test(h) && /^\d+$/.test(m)) {
    const d = new Date(now);
    d.setHours(parseInt(h), parseInt(m), 0, 0);
    const target = parseInt(dow) % 7;
    let diff = (target - d.getDay() + 7) % 7;
    if (diff === 0 && d.getTime() <= now.getTime()) diff = 7;
    d.setDate(d.getDate() + diff);
    return d;
  }
  if (/^\d+$/.test(h) && /^\d+$/.test(m)) {
    const d = new Date(now);
    d.setHours(parseInt(h), parseInt(m), 0, 0);
    if (d.getTime() <= now.getTime()) d.setDate(d.getDate() + 1);
    return d;
  }
  const hMatch = h.match(/^\*\/(\d+)$/);
  const mm = /^\d+$/.test(m) ? parseInt(m) : 0;
  if (hMatch) {
    const step = parseInt(hMatch[1]);
    const d = new Date(now);
    d.setMinutes(mm, 0, 0);
    while (d.getTime() <= now.getTime() || d.getHours() % step !== 0) {
      d.setHours(d.getHours() + 1);
    }
    return d;
  }
  return null;
}

function relative(d: Date | null): string {
  if (!d) return "—";
  const diff = d.getTime() - Date.now();
  if (diff <= 0) return "soon";
  const min = Math.round(diff / 60000);
  if (min < 60) return `in ${min}m`;
  const hr = Math.floor(min / 60);
  const rem = min % 60;
  if (hr < 24) return `in ${hr}h${rem ? ` ${rem}m` : ""}`;
  const day = Math.floor(hr / 24);
  return `in ${day}d`;
}

export function ScheduledTasks() {
  const queryClient = useQueryClient();
  const [adding, setAdding] = useState(false);
  const [form, setForm] = useState({
    type: "say" as TaskType,
    message: "",
    keep: 5,
    preset: "daily" as Preset,
    hour: 4,
    minute: 0,
    hours: 6,
    dow: 0,
  });

  const { data: tasks = [], isLoading } = useQuery<Task[]>({
    queryKey: ["schedule-tasks"],
    queryFn: () => fetch("/api/schedule/tasks").then((r) => r.json()),
    staleTime: 30_000,
  });

  const invalidate = () =>
    queryClient.invalidateQueries({ queryKey: ["schedule-tasks"] });

  const create = useMutation({
    mutationFn: async () => {
      const cron = cronFromForm(form);
      const body: {
        type: TaskType;
        cron: string;
        label: string;
        params: Record<string, unknown>;
      } = { type: form.type, cron, label: "", params: {} };
      if (form.type === "say") body.params.message = form.message.trim();
      if (form.type === "snapshot-prune") body.params.keep = form.keep;
      const res = await fetch("/api/schedule/tasks", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error);
      return data as Task;
    },
    onSuccess: () => {
      toast.success("Task scheduled");
      setAdding(false);
      setForm((f) => ({ ...f, message: "" }));
      invalidate();
    },
    onError: (err) => toast.error("Failed to add task", { description: err.message }),
  });

  const toggle = useMutation({
    mutationFn: async ({ id, enabled }: { id: string; enabled: boolean }) => {
      const res = await fetch("/api/schedule/tasks", {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ id, enabled }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error);
      return data as Task;
    },
    onSuccess: (t) => {
      toast.success(t.enabled ? "Task enabled" : "Task disabled");
      invalidate();
    },
    onError: (err) => toast.error("Toggle failed", { description: err.message }),
  });

  const runNow = useMutation({
    mutationFn: async (id: string) => {
      const res = await fetch("/api/schedule/tasks/run", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ id }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.message || data.error);
      return data;
    },
    onSuccess: () => toast.success("Task ran"),
    onError: (err) => toast.error("Run failed", { description: err.message }),
  });

  const remove = useMutation({
    mutationFn: async (id: string) => {
      const res = await fetch("/api/schedule/tasks", {
        method: "DELETE",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ id }),
      });
      const data = await res.json();
      if (!res.ok) throw new Error(data.error);
      return data;
    },
    onSuccess: () => {
      toast.success("Task removed");
      invalidate();
    },
    onError: (err) => toast.error("Remove failed", { description: err.message }),
  });

  const canSubmit = useMemo(() => {
    if (form.type === "say") {
      const re = /^[^\r\n$`\\'"]{1,120}$/;
      return re.test(form.message.trim());
    }
    if (form.type === "snapshot-prune") {
      return form.keep >= 1 && form.keep <= 50;
    }
    return true;
  }, [form]);

  const pad = (n: number) => String(n).padStart(2, "0");

  return (
    <div className="space-y-3">
      <div className="flex items-center justify-between gap-2 flex-wrap">
        <div>
          <p className="text-sm font-semibold">Scheduled Tasks</p>
          <p className="text-xs text-muted-foreground">
            Recurring chat, backups, and snapshot pruning via crontab
          </p>
        </div>
        {!adding && (
          <Button size="sm" variant="outline" onClick={() => setAdding(true)}>
            Add task
          </Button>
        )}
      </div>

      {adding && (
        <div className="rounded-lg border border-border bg-muted/40 p-3 space-y-3">
          <div className="flex flex-wrap gap-2">
            {(["say", "backup", "snapshot-prune"] as const).map((t) => (
              <button
                key={t}
                onClick={() => setForm((f) => ({ ...f, type: t }))}
                aria-pressed={form.type === t}
                className={`text-xs font-semibold uppercase rounded px-2.5 py-1 border transition ${
                  form.type === t
                    ? "border-primary/40 bg-primary/10 text-primary"
                    : "border-transparent text-muted-foreground hover:text-foreground"
                }`}
              >
                {t === "snapshot-prune" ? "prune" : t}
              </button>
            ))}
          </div>

          {form.type === "say" && (
            <Input
              placeholder="Message to broadcast (e.g. §a[Server] Daily reset soon)"
              value={form.message}
              onChange={(e) => setForm((f) => ({ ...f, message: e.target.value }))}
              maxLength={120}
              className="h-9 text-sm"
            />
          )}

          {form.type === "snapshot-prune" && (
            <div className="flex items-center gap-2">
              <label className="text-xs text-muted-foreground">Keep latest</label>
              <Input
                type="number"
                min={1}
                max={50}
                value={form.keep}
                onChange={(e) =>
                  setForm((f) => ({ ...f, keep: parseInt(e.target.value) || 5 }))
                }
                className="h-9 text-sm w-20"
              />
            </div>
          )}

          <div className="flex flex-wrap items-center gap-2">
            <div className="flex gap-1">
              {(["daily", "every-hours", "weekly"] as const).map((p) => (
                <button
                  key={p}
                  onClick={() => setForm((f) => ({ ...f, preset: p }))}
                  aria-pressed={form.preset === p}
                  className={`text-[10px] font-semibold uppercase rounded px-2 py-1 border transition ${
                    form.preset === p
                      ? "border-primary/40 bg-primary/10 text-primary"
                      : "border-transparent text-muted-foreground hover:text-foreground"
                  }`}
                >
                  {p === "daily" ? "Daily" : p === "weekly" ? "Weekly" : "Every Nh"}
                </button>
              ))}
            </div>

            {form.preset === "weekly" && (
              <select
                value={form.dow}
                onChange={(e) => setForm((f) => ({ ...f, dow: parseInt(e.target.value) }))}
                className="h-9 rounded-md border border-input bg-muted px-2 text-sm"
                aria-label="Day of week"
              >
                {DOW_LABELS.map((d, i) => (
                  <option key={d} value={i}>
                    {d}
                  </option>
                ))}
              </select>
            )}

            {(form.preset === "daily" || form.preset === "weekly") && (
              <>
                <select
                  value={form.hour}
                  onChange={(e) =>
                    setForm((f) => ({ ...f, hour: parseInt(e.target.value) }))
                  }
                  className="h-9 rounded-md border border-input bg-muted px-2 text-sm"
                  aria-label="Hour"
                >
                  {Array.from({ length: 24 }, (_, i) => (
                    <option key={i} value={i}>
                      {pad(i)}
                    </option>
                  ))}
                </select>
                <span className="text-muted-foreground">:</span>
                <select
                  value={form.minute}
                  onChange={(e) =>
                    setForm((f) => ({ ...f, minute: parseInt(e.target.value) }))
                  }
                  className="h-9 rounded-md border border-input bg-muted px-2 text-sm"
                  aria-label="Minute"
                >
                  {[0, 15, 30, 45].map((v) => (
                    <option key={v} value={v}>
                      {pad(v)}
                    </option>
                  ))}
                </select>
              </>
            )}

            {form.preset === "every-hours" && (
              <select
                value={form.hours}
                onChange={(e) =>
                  setForm((f) => ({ ...f, hours: parseInt(e.target.value) }))
                }
                className="h-9 rounded-md border border-input bg-muted px-2 text-sm"
                aria-label="Hour interval"
              >
                {[1, 2, 3, 4, 6, 8, 12].map((v) => (
                  <option key={v} value={v}>
                    every {v}h
                  </option>
                ))}
              </select>
            )}
          </div>

          <div className="flex justify-end gap-2">
            <Button
              size="sm"
              variant="ghost"
              onClick={() => setAdding(false)}
              disabled={create.isPending}
            >
              Cancel
            </Button>
            <Button
              size="sm"
              onClick={() => create.mutate()}
              disabled={!canSubmit || create.isPending}
            >
              {create.isPending ? "Adding..." : "Add task"}
            </Button>
          </div>
        </div>
      )}

      {isLoading ? (
        <p className="text-xs text-muted-foreground">Loading...</p>
      ) : tasks.length === 0 ? (
        <p className="text-xs text-muted-foreground">No scheduled tasks yet.</p>
      ) : (
        <ul className="space-y-1">
          {tasks.map((t) => (
            <li
              key={t.id}
              className={`flex items-center justify-between gap-2 px-3 py-2 rounded-md bg-muted/50 group ${
                t.enabled ? "" : "opacity-60"
              }`}
            >
              <div className="min-w-0 flex-1">
                <div className="flex items-center gap-2 flex-wrap">
                  <span className="text-sm font-medium truncate">{t.label}</span>
                  <Badge variant="secondary" className="text-xs px-1.5 py-0">
                    {t.type}
                  </Badge>
                  {!t.enabled && (
                    <Badge
                      variant="outline"
                      className="text-xs px-1.5 py-0 border-muted-foreground/40 text-muted-foreground"
                    >
                      disabled
                    </Badge>
                  )}
                </div>
                <p className="text-xs text-muted-foreground">
                  {describeCron(t.cron)}
                  {t.enabled && <> · next {relative(nextRun(t.cron))}</>}
                </p>
              </div>
              <div className="flex gap-1 shrink-0 flex-wrap justify-end">
                <Button
                  size="sm"
                  variant="ghost"
                  onClick={() => runNow.mutate(t.id)}
                  disabled={runNow.isPending}
                  className="text-xs h-9 sm:opacity-0 sm:group-hover:opacity-100 transition"
                  title="Run immediately"
                >
                  Run now
                </Button>
                <Button
                  size="sm"
                  variant="ghost"
                  onClick={() => toggle.mutate({ id: t.id, enabled: !t.enabled })}
                  disabled={toggle.isPending}
                  className="text-xs h-9 sm:opacity-0 sm:group-hover:opacity-100 transition"
                >
                  {t.enabled ? "Disable" : "Enable"}
                </Button>
                <Button
                  size="sm"
                  variant="ghost"
                  onClick={() => remove.mutate(t.id)}
                  disabled={remove.isPending}
                  className="text-xs h-9 text-muted-foreground sm:opacity-0 sm:group-hover:opacity-100 transition"
                >
                  Remove
                </Button>
              </div>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}
