'use client';

import { useEffect, useRef, useState } from 'react';
import Link from 'next/link';
import { useParams } from 'next/navigation';
import { Check, Circle, Loader2, ExternalLink } from 'lucide-react';
import { toast } from 'sonner';
import { useQueryClient } from '@tanstack/react-query';

import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { apiFetch } from '@/lib/api/client';

interface OnboardingStep {
  id: string;
  href: string;
  label: string;
  description: string;
  /** Setting key whose presence proves the step is done. When set, the
   *  checkmark auto-fills from the settings list. When undefined, the
   *  step relies on the manual checkbox in `onboarding_status`. */
  autoCheckSettingKey?: string;
  /** Multi-key gate: all listed setting keys must be present (non-empty)
   *  for the step to auto-complete. Useful for compound checks where a
   *  single key would falsely mark "done" - e.g. Documenso needs a URL
   *  plus signer identity plus a template id, not just the URL. */
  autoCheckSettingKeysAll?: readonly string[];
  /** Override: read this many users / tags / roles from a list endpoint
   *  and consider the step done when count > 0. */
  autoCheckListEndpoint?: string;
}

const STEPS: OnboardingStep[] = [
  {
    id: 'branding',
    href: 'branding',
    label: 'Set port name, logo, primary colour',
    description: 'Branding flows into the navbar, emails, EOI PDFs, and the public auth shell.',
    autoCheckSettingKey: 'branding_logo_url',
  },
  {
    id: 'email',
    href: 'email',
    label: 'Configure outgoing email',
    description:
      'From-address, signature, footer, plus per-port SMTP overrides if you don’t use the global account.',
    autoCheckSettingKey: 'smtp_host_override',
  },
  {
    id: 'documenso',
    href: 'documenso',
    label: 'Connect Documenso for EOIs',
    description:
      'API credentials, the EOI template id, plus the developer + approver identity that signs every EOI.',
    // Compound gate: an EOI cannot be sent without ALL of these. A
    // port-admin who saves only the URL would otherwise see the step go
    // green and discover the gap on first EOI attempt (Documenso 404s
    // on a missing template, or sends recipients with empty names).
    autoCheckSettingKeysAll: [
      'documenso_api_url_override',
      'documenso_developer_email',
      'documenso_approver_email',
      'documenso_eoi_template_id',
    ],
  },
  {
    id: 'settings',
    href: 'settings',
    label: 'Tune business rules + recommender weights',
    description:
      'Pipeline weights, net-10 discount, berth recommender knobs (heat weights, fall-through policy).',
    // Recommender defaults are layered (port > global > built-in), so a
    // port that uses the built-ins never writes a row. Use a tuned
    // heat-weight as the "admin actually saw + chose" sentinel instead.
    autoCheckSettingKey: 'heat_weight_recency',
  },
  {
    id: 'roles',
    href: 'roles',
    label: 'Create roles & assign users',
    description: 'Per-port roles inherit from system roles; override permissions here.',
    autoCheckListEndpoint: '/api/v1/admin/roles',
  },
  {
    id: 'users',
    href: 'users',
    label: 'Invite the rest of the team',
    description:
      'Invite users, assign roles, optionally grant residential access. Track pending vs accepted.',
    autoCheckListEndpoint: '/api/v1/admin/users',
  },
  {
    id: 'tags',
    href: 'tags',
    label: 'Define starter tags',
    description: 'Color-coded labels used across clients, yachts, companies, and interests.',
    autoCheckListEndpoint: '/api/v1/tags/options',
  },
  {
    id: 'storage',
    href: 'storage',
    label: 'Configure storage backend',
    description:
      'Verify S3/filesystem and run a test connection before going live so PDFs and avatars persist correctly.',
    autoCheckSettingKey: 'storage_backend',
  },
  {
    id: 'forms',
    href: 'forms',
    label: 'Wire the website intake forms',
    description:
      'Inquiry forms on the marketing site dual-write into the CRM via /api/public/website-inquiries. Manually mark complete when verified.',
  },
];

interface ResolvedValue {
  isSet: boolean;
  source?: 'port' | 'global' | 'env' | 'default' | 'none';
  value?: unknown;
}
interface ResolvedResp {
  data: { entries: Array<{ key: string }>; values: Record<string, ResolvedValue> };
}
interface SettingRow {
  key: string;
  value: unknown;
  portId: string | null;
}
interface SettingsResp {
  data: { portSettings: SettingRow[]; globalSettings: SettingRow[] };
}

export function OnboardingChecklist() {
  const params = useParams<{ portSlug: string }>();
  const portSlug = params?.portSlug ?? '';
  const [autoChecks, setAutoChecks] = useState<Record<string, boolean>>({});
  // Per-step source flags - populated for steps whose auto-check resolved
  // via the env / default fallback rather than a port / global override.
  // Surfaces "Resolving from env" copy so super admins see what's
  // backing each green tick without digging into the settings page.
  const [autoSources, setAutoSources] = useState<Record<string, ResolvedValue['source']>>({});
  const [manualChecks, setManualChecks] = useState<Record<string, boolean>>({});
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState<string | null>(null);

  useEffect(() => {
    async function load() {
      setLoading(true);
      try {
        // Collect every setting key referenced by the checklist so we can
        // batch-resolve them through the full chain in one round-trip.
        // The `resolved` endpoint reads port→global→env→default, so a
        // port using env-only credentials still auto-ticks (the old
        // raw `/admin/settings` query missed env fallback entirely).
        const keys = new Set<string>();
        for (const s of STEPS) {
          if (s.autoCheckSettingKey) keys.add(s.autoCheckSettingKey);
          if (s.autoCheckSettingKeysAll) for (const k of s.autoCheckSettingKeysAll) keys.add(k);
        }
        // Manual-checkbox state still lives in the raw system_settings
        // row (it's a JSON blob, not a per-key registry entry) - keep
        // fetching it the old way.
        const [resolved, settings] = await Promise.all([
          keys.size > 0
            ? apiFetch<ResolvedResp>(
                `/api/v1/admin/settings/resolved?keys=${Array.from(keys)
                  .map(encodeURIComponent)
                  .join(',')}`,
              )
            : Promise.resolve({ data: { entries: [], values: {} } } as ResolvedResp),
          apiFetch<SettingsResp>('/api/v1/admin/settings'),
        ]);
        const values = resolved.data.values;
        const isPresent = (key: string): boolean => Boolean(values[key]?.isSet);

        const checks: Record<string, boolean> = {};
        const listChecks = await Promise.all(
          STEPS.map(async (s) => {
            if (s.autoCheckSettingKey) {
              return [s.id, isPresent(s.autoCheckSettingKey)] as const;
            }
            if (s.autoCheckSettingKeysAll) {
              return [s.id, s.autoCheckSettingKeysAll.every((k) => isPresent(k))] as const;
            }
            if (s.autoCheckListEndpoint) {
              try {
                const res = await apiFetch<{ data: unknown[] }>(s.autoCheckListEndpoint);
                return [s.id, Array.isArray(res.data) && res.data.length > 0] as const;
              } catch {
                return [s.id, false] as const;
              }
            }
            return [s.id, false] as const;
          }),
        );
        for (const [id, done] of listChecks) checks[id] = done;
        setAutoChecks(checks);

        // Capture the dominant source per step. For single-key checks this
        // is the key's source; for multi-key checks we report the
        // "weakest" source so the rep sees env if any sub-key is env.
        const sourcesByStep: Record<string, ResolvedValue['source']> = {};
        const PRIORITY: Record<string, number> = {
          port: 4,
          global: 3,
          env: 2,
          default: 1,
          none: 0,
        };
        for (const s of STEPS) {
          if (s.autoCheckSettingKey) {
            sourcesByStep[s.id] = values[s.autoCheckSettingKey]?.source;
          } else if (s.autoCheckSettingKeysAll) {
            const sources = s.autoCheckSettingKeysAll
              .map((k) => values[k]?.source)
              .filter((x): x is NonNullable<ResolvedValue['source']> => Boolean(x));
            // Pick the lowest-priority (weakest) source so the rep sees
            // "env" if the compound has any env-only sub-key.
            sources.sort((a, b) => (PRIORITY[a] ?? 0) - (PRIORITY[b] ?? 0));
            sourcesByStep[s.id] = sources[0];
          }
        }
        setAutoSources(sourcesByStep);

        // Pull the manual-checkbox state from system_settings.
        const allSettings = [...settings.data.portSettings, ...settings.data.globalSettings];
        const byKey = new Map(allSettings.map((r) => [r.key, r.value]));
        const manual = (byKey.get('onboarding_manual_status') ?? {}) as Record<string, boolean>;
        setManualChecks(manual);
      } finally {
        setLoading(false);
      }
    }
    void load();
  }, []);

  async function toggleManual(id: string) {
    const next = { ...manualChecks, [id]: !manualChecks[id] };
    setManualChecks(next);
    setSaving(id);
    try {
      await apiFetch('/api/v1/admin/settings', {
        method: 'PUT',
        body: { key: 'onboarding_manual_status', value: next },
      });
    } finally {
      setSaving(null);
    }
  }

  const stepDone = (id: string) => Boolean(autoChecks[id]) || Boolean(manualChecks[id]);
  const completed = STEPS.filter((s) => stepDone(s.id)).length;
  const percent = Math.round((completed / STEPS.length) * 100);

  // Fire a celebration toast exactly once when the last item ticks. Both
  // auto- and manual-driven completions trigger it. Guarded against
  // refetch noise so reloading the page when already 100% doesn't re-fire.
  const queryClient = useQueryClient();
  const prevCompletedRef = useRef<number | null>(null);
  useEffect(() => {
    if (loading) return;
    const prev = prevCompletedRef.current;
    if (prev !== null && prev < STEPS.length && completed === STEPS.length) {
      toast.success('Setup complete — every onboarding step is checked off.', {
        duration: 6000,
      });
      // Invalidate the shared status query so the banner + tile collapse
      // immediately instead of waiting for the 60s cache to expire.
      queryClient.invalidateQueries({ queryKey: ['admin', 'onboarding-status'] });
    }
    prevCompletedRef.current = completed;
  }, [completed, loading, queryClient]);

  return (
    <div className="mt-6 space-y-6">
      <Card>
        <CardHeader>
          <CardTitle>Setup checklist</CardTitle>
          <CardDescription>
            {completed} of {STEPS.length} complete. Auto-checked steps update when you save the
            underlying setting; manual ones (like website-form integration) need the checkbox.
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-5">
          <Progress value={percent} className="h-2" />

          <ol className="space-y-3">
            {STEPS.map((step, idx) => {
              const auto = Boolean(autoChecks[step.id]);
              const manual = Boolean(manualChecks[step.id]);
              const done = auto || manual;
              return (
                <li
                  key={step.id}
                  className={
                    done
                      ? 'flex items-start gap-3 rounded-md border border-emerald-200 bg-emerald-50/50 p-3'
                      : 'flex items-start gap-3 rounded-md border p-3'
                  }
                >
                  <span
                    className={
                      done
                        ? 'mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-emerald-500 text-white'
                        : 'mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground'
                    }
                  >
                    {done ? (
                      <Check className="h-4 w-4" aria-hidden />
                    ) : loading ? (
                      <Loader2 className="h-4 w-4 animate-spin" aria-hidden />
                    ) : (
                      <Circle className="h-4 w-4" aria-hidden />
                    )}
                  </span>
                  <div className="flex-1 min-w-0">
                    <div className="flex items-start justify-between gap-2">
                      <div>
                        <Link
                          href={`/${portSlug}/admin/${step.href}` as never}
                          className="text-sm font-medium hover:underline inline-flex items-center gap-1"
                        >
                          {idx + 1}. {step.label}
                          <ExternalLink className="h-3 w-3 opacity-50" aria-hidden />
                        </Link>
                        <p className="mt-0.5 text-xs text-muted-foreground">{step.description}</p>
                        {auto && (
                          <p className="mt-1 text-xs text-emerald-700">
                            Auto-detected complete via{' '}
                            <code className="text-xs">
                              {step.autoCheckSettingKey ??
                                step.autoCheckSettingKeysAll?.join(' + ') ??
                                step.autoCheckListEndpoint}
                            </code>
                            {autoSources[step.id] && autoSources[step.id] !== 'port' ? (
                              <span className="ml-1 text-xs text-amber-700">
                                · resolving from{' '}
                                <strong className="font-medium">
                                  {autoSources[step.id] === 'env'
                                    ? 'env fallback'
                                    : autoSources[step.id] === 'global'
                                      ? 'global default'
                                      : autoSources[step.id] === 'default'
                                        ? 'built-in default'
                                        : autoSources[step.id]}
                                </strong>
                              </span>
                            ) : null}
                          </p>
                        )}
                      </div>
                      {!auto && (
                        <Button
                          size="sm"
                          variant={manual ? 'secondary' : 'outline'}
                          disabled={saving === step.id}
                          onClick={() => toggleManual(step.id)}
                        >
                          {saving === step.id ? (
                            <Loader2 className="h-3.5 w-3.5 animate-spin" aria-hidden />
                          ) : manual ? (
                            'Mark incomplete'
                          ) : (
                            'Mark done'
                          )}
                        </Button>
                      )}
                    </div>
                  </div>
                </li>
              );
            })}
          </ol>
        </CardContent>
      </Card>
    </div>
  );
}
