import { useMemo, useState } from 'react';
import {
  Calendar, Clock, Database, FileArchive, Plus, Save, Trash2, X, Package,
} from 'lucide-react';
import { Card, CardBody, CardHeader, CardTitle, CardDescription, CardFooter } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { Field, Input, Switch } from '@/components/ui/Input';
import { Combobox } from '@/components/ui/Combobox';
import { Loading, ErrorState, Empty, Toast } from '@/components/ui/States';
import { api, type Schedule, type ScheduleFrequency } from '@/lib/api';
import { formatDateTime } from '@/lib/format';
import { useQuery, useMutation } from '@/hooks/useQuery';
import { useConfirm } from '@/components/ConfirmProvider';
import { cn } from '@/lib/cn';

const frequencies: ScheduleFrequency[] = ['hourly', 'daily', 'weekly', 'monthly'];

const TYPE_META = {
  full:     { label: 'Full',     icon: Package,     tint: 'bg-brand-50 text-brand-700' },
  database: { label: 'Database', icon: Database,    tint: 'bg-sky-50 text-sky-700' },
  files:    { label: 'Files',    icon: FileArchive, tint: 'bg-amber-50 text-amber-700' },
} as const;

type Draft = Omit<Schedule, 'id' | 'created_at'> & { id?: string };

const emptyDraft = (): Draft => ({
  name: 'New schedule',
  type: 'full',
  frequency: 'daily',
  time_of_day: '03:00',
  retention: 7,
  enabled: true,
  destinations: [], // empty = "every connected destination"
});

export function Schedule() {
  const { data, isLoading, error, refetch } = useQuery(() => api.schedules.list(), []);
  const [editing, setEditing] = useState<Draft | null>(null);
  const [toast, setToast] = useState<{ msg: string; tone: 'success' | 'danger' } | null>(null);
  const confirm = useConfirm();

  const items = data?.items ?? [];
  const upcoming = data?.next ?? [];

  // Manual escape hatch for the "phantom backup keeps firing" case — see the
  // header button below for context.
  const purgeCron = useMutation<void, { removed: number }>(
    () => api.schedules.purgeCron(),
  );

  if (isLoading) return <Loading label="Loading schedules…" />;
  if (error)     return <ErrorState error={error} onRetry={refetch} />;

  return (
    <div className="space-y-5 max-w-[1400px]">
      <Card>
        <CardBody className="flex items-center justify-between">
          <div>
            <h2 className="text-base font-semibold text-ink-900">Backup schedules</h2>
            <p className="text-xs text-ink-500 mt-0.5">
              Combine schedules to fit your strategy. For example: daily database, weekly full, monthly archive.
            </p>
          </div>
          <div className="flex items-center gap-2">
            <Button
              variant="ghost"
              size="sm"
              title="Clear every queued scheduled-backup event from the WP cron table. Use this if backups keep firing automatically despite having no schedules. Stale events left by an old plugin version or a restored database can sometimes do that."
              onClick={async () => {
                const ok = await confirm({
                  title: 'Purge stale cron events?',
                  description: 'Removes every scheduled-backup event currently queued in WordPress cron, then any active schedules below get re-registered automatically on the next page load. Safe to run anytime. Your schedules are not deleted.',
                  confirmLabel: 'Purge cron events',
                  tone: 'danger',
                });
                if (!ok) return;
                try {
                  const r = await purgeCron.mutate();
                  setToast({ msg: `Removed ${r.removed} stale cron event(s)`, tone: 'success' });
                  refetch();
                } catch (e: any) { setToast({ msg: e.message ?? String(e), tone: 'danger' }); }
              }}
            >
              Purge cron events
            </Button>
            <Button variant="primary" leftIcon={<Plus className="h-4 w-4" />} onClick={() => setEditing(emptyDraft())}>
              New schedule
            </Button>
          </div>
        </CardBody>
      </Card>

      <div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
        {/* Schedules list */}
        <div className="lg:col-span-2 space-y-3">
          {items.length === 0 && !editing && (
            <Empty
              title="No schedules yet"
              hint="Create your first schedule. Most setups start with a daily full backup."
              action={<Button variant="primary" leftIcon={<Plus className="h-4 w-4" />} onClick={() => setEditing(emptyDraft())}>New schedule</Button>}
            />
          )}

          {items.map((s) => (
            <ScheduleRow
              key={s.id}
              schedule={s}
              onToggle={async (enabled) => {
                try { await api.schedules.update(s.id, { enabled }); refetch(); }
                catch (e: any) { setToast({ msg: e?.message ?? String(e), tone: 'danger' }); }
              }}
              onEdit={() => setEditing({ ...s })}
              onDelete={async () => {
                const ok = await confirm({
                  title: `Delete schedule "${s.name}"?`,
                  description: 'The cron job will be cancelled. Existing backups created by this schedule remain.',
                  confirmLabel: 'Delete',
                  tone: 'danger',
                });
                if (!ok) return;
                try {
                  await api.schedules.remove(s.id);
                  refetch();
                  setToast({ msg: `${s.name} deleted`, tone: 'success' });
                } catch (e: any) { setToast({ msg: e?.message ?? String(e), tone: 'danger' }); }
              }}
            />
          ))}

          {editing && (
            <ScheduleForm
              draft={editing}
              onCancel={() => setEditing(null)}
              onSaved={() => { setEditing(null); refetch(); setToast({ msg: 'Schedule saved', tone: 'success' }); }}
              onError={(m) => setToast({ msg: m, tone: 'danger' })}
            />
          )}
        </div>

        {/* Upcoming runs */}
        <div>
          <Card>
            <CardHeader>
              <CardTitle>Next 5 runs</CardTitle>
            </CardHeader>
            <CardBody className="space-y-3">
              {upcoming.length === 0 ? (
                <p className="text-xs text-ink-500">No upcoming runs. Enable a schedule to populate this list.</p>
              ) : (
                upcoming.map((r, i) => {
                  const meta = TYPE_META[r.type] ?? TYPE_META.full;
                  return (
                    <div key={i} className="flex items-center gap-3 p-2.5 rounded-lg border border-ink-100">
                      <div className="h-8 w-8 grid place-items-center rounded-lg bg-ink-100 text-ink-700">
                        <Calendar className="h-4 w-4" />
                      </div>
                      <div className="flex-1 min-w-0">
                        <p className="text-sm font-medium text-ink-900 truncate">
                          {formatDateTime(r.at)}
                        </p>
                        <p className="text-[11px] text-ink-500 truncate">{r.schedule_name} · {meta.label}</p>
                      </div>
                      <Badge tone="neutral" dot>queued</Badge>
                    </div>
                  );
                })
              )}
            </CardBody>
          </Card>
        </div>
      </div>

      {toast && <Toast message={toast.msg} tone={toast.tone} onClose={() => setToast(null)} />}
    </div>
  );
}

/* ──────────────────────────────────────────────────────────────────────── */

/**
 * One row in the destination picker. Click anywhere on the row to toggle.
 * The visible checkbox + brand-coloured selected state make the affordance
 * obvious so users don't think it's a static label.
 */
function DestinationPickerRow({
  checked, onChange, title, subtitle, isMaster,
}: {
  checked: boolean;
  onChange: () => void;
  title: string;
  subtitle?: React.ReactNode;
  isMaster?: boolean;
}) {
  return (
    <button
      type="button"
      onClick={onChange}
      aria-pressed={checked}
      className={cn(
        'w-full flex items-start gap-3 px-3.5 py-3 text-left transition-colors',
        checked ? 'bg-brand-50/60' : 'bg-white hover:bg-ink-50',
        isMaster && 'border-b border-ink-100',
      )}
    >
      {/* Custom checkbox. Both states need to be clearly visible:
          the unchecked variant uses a darker border + light fill so it
          reads as a real "you can click here" box even on a white row,
          not just empty space that happens to be next to a label. */}
      <span
        className={cn(
          'mt-0.5 h-[18px] w-[18px] grid place-items-center rounded-[5px] border-2 shrink-0 transition-colors',
          checked
            ? 'border-brand-500 bg-brand-500 text-white'
            : 'border-ink-400 bg-ink-50',
        )}
        aria-hidden
      >
        {checked && (
          <svg viewBox="0 0 12 12" className="h-3 w-3" fill="none" stroke="currentColor" strokeWidth="2.5">
            <path d="M2.5 6.5 L5 9 L9.5 3.5" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        )}
      </span>
      <span className="flex-1 min-w-0">
        <span className={cn(
          'block text-sm font-medium',
          checked ? 'text-brand-900' : 'text-ink-900',
        )}>{title}</span>
        {subtitle && (
          <span className="block text-[11px] text-ink-500 mt-0.5">{subtitle}</span>
        )}
      </span>
    </button>
  );
}

function ScheduleRow({
  schedule, onToggle, onEdit, onDelete,
}: {
  schedule: Schedule;
  onToggle: (v: boolean) => void;
  onEdit: () => void;
  onDelete: () => void;
}) {
  const meta = TYPE_META[schedule.type] ?? TYPE_META.full;
  const Icon = meta.icon;

  return (
    <Card>
      <CardBody className="flex items-center gap-4 flex-wrap">
        <span className={cn('h-10 w-10 grid place-items-center rounded-xl shrink-0', meta.tint)}>
          <Icon className="h-5 w-5" />
        </span>
        <div className="flex-1 min-w-0">
          <div className="flex items-center gap-2 flex-wrap">
            <p className="text-sm font-semibold text-ink-900">{schedule.name}</p>
            <Badge tone="neutral">{meta.label}</Badge>
          </div>
          <p className="text-xs text-ink-500 mt-1 flex items-center gap-1.5 flex-wrap">
            <Clock className="h-3 w-3" />
            <span>
              {schedule.frequency === 'hourly'
                ? `every hour at :${(schedule.time_of_day.split(':')[1] ?? '00').padStart(2, '0')}`
                : `${schedule.frequency} at ${schedule.time_of_day} UTC`}
              {' · '}
              keep last {schedule.retention}
            </span>
            <span aria-hidden>·</span>
            <span>
              {(!schedule.destinations || schedule.destinations.length === 0)
                ? 'all connected destinations'
                : `${schedule.destinations.length} destination${schedule.destinations.length === 1 ? '' : 's'}`}
            </span>
          </p>
        </div>
        <Switch checked={schedule.enabled} onChange={onToggle} />
        <Button size="sm" variant="ghost" onClick={onEdit}>Edit</Button>
        <button onClick={onDelete} title="Delete" aria-label={`Delete schedule ${schedule.name}`}
          className="h-8 w-8 grid place-items-center rounded-md text-ink-400 hover:text-danger hover:bg-red-50">
          <Trash2 className="h-4 w-4" />
        </button>
      </CardBody>
    </Card>
  );
}

/* ──────────────────────────────────────────────────────────────────────── */

function ScheduleForm({
  draft, onCancel, onSaved, onError,
}: {
  draft: Draft;
  onCancel: () => void;
  onSaved: () => void;
  onError: (m: string) => void;
}) {
  const isNew = !draft.id;
  const [form, setForm] = useState<Draft>(draft);

  // Connected destinations populate the picker. We don't auto-poll;
  // the form is short-lived (open → edit → save → close) so a one-shot
  // fetch is enough. Paused destinations stay in the list with a hint —
  // the user might be picking them deliberately knowing they're paused.
  const destinationsQuery = useQuery(() => api.destinations.list(), []);
  const allDestinations = destinationsQuery.data?.items ?? [];

  const save = useMutation(
    async () => {
      if (isNew) {
        return api.schedules.create({
          name: form.name, type: form.type, frequency: form.frequency,
          time_of_day: form.time_of_day, retention: form.retention, enabled: form.enabled,
          destinations: form.destinations,
        });
      }
      return api.schedules.update(form.id!, form);
    },
    {
      onSuccess: onSaved,
      onError: (e: any) => onError(e?.message ?? String(e)),
    },
  );

  const timeOptions = useMemo(
    () => Array.from({ length: 24 * 2 }, (_, i) => {
      const h = Math.floor(i / 2); const m = i % 2 === 0 ? '00' : '30';
      const v = `${String(h).padStart(2, '0')}:${m}`;
      const display = `${((h + 11) % 12) + 1}:${m} ${h < 12 ? 'AM' : 'PM'}`;
      return { value: v, label: v, hint: display };
    }),
    [],
  );

  return (
    <Card className="border-brand-300">
      <CardHeader>
        <div className="flex items-start gap-3 flex-1">
          <span className="h-8 w-8 grid place-items-center rounded-lg bg-brand-50 text-brand-700 shrink-0">
            <Calendar className="h-4 w-4" />
          </span>
          <div>
            <CardTitle>{isNew ? 'New schedule' : `Edit ${draft.name}`}</CardTitle>
            <CardDescription>Pick a backup type, cadence, and how many to keep.</CardDescription>
          </div>
        </div>
        <button onClick={onCancel} title="Cancel" aria-label="Cancel schedule edit"
          className="h-8 w-8 grid place-items-center rounded-md text-ink-400 hover:text-ink-900 hover:bg-ink-100">
          <X className="h-4 w-4" />
        </button>
      </CardHeader>

      <CardBody className="space-y-4">
        <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
          <Field label="Schedule name" hint="Visible in the list and logs">
            <Input value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} />
          </Field>
          <Field label="Backup type">
            <Combobox
              value={form.type}
              onChange={(v) => setForm({ ...form, type: v as Draft['type'] })}
              options={[
                { value: 'full',     label: 'Full',     hint: 'Files + database' },
                { value: 'database', label: 'Database', hint: 'Tables only' },
                { value: 'files',    label: 'Files',    hint: 'Uploads, themes, plugins' },
              ]}
            />
          </Field>
        </div>

        <div>
          <p className="text-xs font-medium text-ink-700 mb-2">Cadence</p>
          <div className="grid grid-cols-4 gap-2">
            {frequencies.map((f) => (
              <button key={f} onClick={() => setForm({ ...form, frequency: f })}
                className={cn(
                  'h-9 rounded-lg text-xs font-medium border-2 transition-colors capitalize',
                  form.frequency === f ? 'bg-ink-900 text-white border-ink-900' : 'bg-white text-ink-700 border-ink-200 hover:bg-ink-50',
                )}>
                {f}
              </button>
            ))}
          </div>
        </div>

        <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
          {form.frequency === 'hourly' ? (
            // Hourly schedules fire every hour from the first run on. Only
            // the minute-past-the-hour is meaningful; the HH gets ignored
            // after the initial registration. So we show ONLY a minute
            // picker. Stored format stays HH:MM for backend compatibility.
            <Field label="Minute past the hour" hint="Backup fires at this minute every hour">
              <Combobox
                value={(form.time_of_day.split(':')[1] ?? '00').padStart(2, '0')}
                onChange={(v) => setForm({ ...form, time_of_day: `00:${v}` })}
                options={[
                  { value: '00', label: ':00' },
                  { value: '15', label: ':15' },
                  { value: '30', label: ':30' },
                  { value: '45', label: ':45' },
                ]}
              />
            </Field>
          ) : (
            <Field label="Time of day" hint="Server timezone (UTC)">
              <Combobox
                value={form.time_of_day}
                onChange={(v) => setForm({ ...form, time_of_day: v })}
                options={timeOptions}
                searchPlaceholder="Type to filter…"
              />
            </Field>
          )}
          <Field label="Retention" hint={`Keep last ${form.retention} successful backups of this type`}>
            <input type="range" min={1} max={60} value={form.retention}
              onChange={(e) => setForm({ ...form, retention: parseInt(e.target.value) })}
              className="w-full accent-brand-500 h-9" />
          </Field>
        </div>

        {/* Destination picker. "All connected" toggles the picker into
            empty-list mode (server interprets empty as every connected
            destination). Otherwise the user picks specific destinations
            as a multi-select list. Card-style rows with a visible
            checkbox so the affordance is obvious even on a busy page. */}
        <div>
          <p className="text-xs font-medium text-ink-700 mb-2">Destinations</p>
          {destinationsQuery.isLoading ? (
            <p className="text-[11px] text-ink-500 py-3">Loading destinations…</p>
          ) : allDestinations.length === 0 ? (
            <div className="rounded-lg border border-amber-200 bg-amber-50 p-3 text-xs text-amber-900">
              No destinations configured. The schedule will run but the backup has nowhere to land.
              Add a destination first under <strong>Destinations</strong>.
            </div>
          ) : (
            <div className="rounded-lg border border-ink-200 bg-white divide-y divide-ink-100 overflow-hidden">
              {/* "Send to all" master option. Selecting it clears the per-
                  destination picks (server stores empty array = "all"). */}
              <DestinationPickerRow
                checked={form.destinations.length === 0}
                onChange={() => setForm({ ...form, destinations: [] })}
                title="Send to all connected destinations"
                subtitle="Resolved at run time. New destinations added later are auto-included."
                isMaster
              />
              {allDestinations.map((d) => {
                const picked = form.destinations.includes(d.id);
                return (
                  <DestinationPickerRow
                    key={d.id}
                    checked={picked}
                    onChange={() => {
                      const next = picked
                        ? form.destinations.filter((x) => x !== d.id)
                        : [...form.destinations, d.id];
                      setForm({ ...form, destinations: next });
                    }}
                    title={d.name}
                    subtitle={
                      <span className="inline-flex items-center gap-1.5">
                        <span className="uppercase text-[9px] tracking-wider px-1.5 py-0.5 rounded bg-ink-100 text-ink-600">{d.type}</span>
                        {d.paused && (
                          <span className="uppercase text-[9px] tracking-wider px-1.5 py-0.5 rounded bg-amber-100 text-amber-800">paused</span>
                        )}
                        {d.status !== 'connected' && !d.paused && (
                          <span className="uppercase text-[9px] tracking-wider px-1.5 py-0.5 rounded bg-rose-100 text-rose-800">{d.status}</span>
                        )}
                      </span>
                    }
                  />
                );
              })}
            </div>
          )}
          <p className="text-[11px] text-ink-500 mt-2">
            {form.destinations.length === 0
              ? 'Backups go to every connected destination at run time.'
              : `Backups go ONLY to the ${form.destinations.length} selected destination${form.destinations.length === 1 ? '' : 's'}.`}
          </p>
        </div>

        <div className="flex items-center justify-between p-3 rounded-lg border border-ink-200 bg-white">
          <div>
            <p className="text-sm font-medium text-ink-800">Enabled</p>
            <p className="text-[11px] text-ink-500">Disabled schedules don't run but stay configured.</p>
          </div>
          <Switch checked={form.enabled} onChange={(v) => setForm({ ...form, enabled: v })} />
        </div>
      </CardBody>

      <CardFooter className="justify-end">
        <Button variant="ghost" onClick={onCancel}>Cancel</Button>
        <Button variant="primary" leftIcon={<Save className="h-4 w-4" />} disabled={save.isLoading}
          onClick={() => save.mutate(undefined as any)}>
          {save.isLoading ? 'Saving…' : (isNew ? 'Create schedule' : 'Save changes')}
        </Button>
      </CardFooter>
    </Card>
  );
}
