import { useEffect, useRef, useState } from 'react';
import {
  Upload, FileArchive, X, CheckCircle2, AlertCircle, AlertTriangle,
  Server, RefreshCw, Loader2, FolderSearch, HardDrive,
} from 'lucide-react';
import { Card, CardHeader, CardTitle, CardBody } from './ui/Card';
import { Button } from './ui/Button';
import { Progress } from './ui/Progress';
import { api, bk, type Destination } from '@/lib/api';
import { formatBytes, formatDateTime } from '@/lib/format';
import { cn } from '@/lib/cn';

/** A backup part is any .zip / .zip.enc archive or a .sql(.gz)(.enc) database dump. */
const PART_RE = /\.(zip|gz|sql|enc)$/i;

/** Destination types that can be browsed for backups (see list_backups()). */
const BROWSABLE_TYPES = ['local', 'ftp', 'sftp', 's3', 'aws', 'wasabi'];

type Mode = 'upload' | 'folder' | 'source';
type FolderScan = {
  folder: string;
  parts: Array<{ name: string; category: string; size: number }>;
  unknown: string[];
};
type SourceBackup = {
  ext_id: string;
  already_imported: boolean;
  has_manifest: boolean;
  type: string;
  created_at: string;
  source_url: string;
  parts: number;
  size: number;
};

/**
 * Inline backup-import card. Sits on the Backups page beneath the list.
 *
 * Three ways in, since some hosts' firewalls block large HTTP uploads outright:
 *  - Upload parts — chunked, gzipped, firewall-tolerant HTTP upload.
 *  - Server folder — the user copies the parts onto the server via FTP / file
 *    manager (never firewall-inspected) and the plugin assembles them.
 *  - From destination — browse a configured storage destination for backups
 *    (including ones made on another site) and import one. The cross-server
 *    migration path: no upload at all, the restore engine pulls the bytes.
 */
export function ImportBackup({
  open,
  onClose,
  onImported,
}: {
  open: boolean;
  onClose: () => void;
  onImported: () => void;
}) {
  const [mode, setMode]         = useState<Mode>('upload');
  const [files, setFiles]       = useState<File[]>([]);
  const [progress, setProgress] = useState(0);
  const [busy, setBusy]         = useState(false);
  const [error, setError]       = useState<string | null>(null);
  const [drag, setDrag]         = useState(false);
  const [scan, setScan]         = useState<FolderScan | null>(null);
  const [srcDests, setSrcDests]     = useState<Destination[]>([]);
  const [srcDestId, setSrcDestId]   = useState('');
  const [srcBackups, setSrcBackups] = useState<SourceBackup[] | null>(null);
  const inputRef = useRef<HTMLInputElement>(null);

  const reset = () => {
    setMode('upload'); setFiles([]); setProgress(0); setBusy(false);
    setError(null); setDrag(false); setScan(null);
    setSrcDests([]); setSrcDestId(''); setSrcBackups(null);
  };

  // Reset state whenever the card is closed
  useEffect(() => { if (!open) reset(); }, [open]);

  // Warn the user before closing/reloading the tab during an upload.
  useEffect(() => {
    if (!busy) return;
    const handler = (e: BeforeUnloadEvent) => {
      e.preventDefault();
      e.returnValue = ''; // Required for Chrome
      return '';
    };
    window.addEventListener('beforeunload', handler);
    return () => window.removeEventListener('beforeunload', handler);
  }, [busy]);

  const runScan = async () => {
    setBusy(true); setError(null);
    try { setScan(await api.backups.importFolder.scan()); }
    catch (e: any) { setError(e?.message ?? String(e)); }
    finally { setBusy(false); }
  };

  // Auto-scan the server folder the first time the user opens that tab.
  // Keyed off `open` + `mode` only. Adding `scan` / `busy` to the deps
  // would cause a self-retrigger storm: scan finishes → setScan → effect
  // sees `!scan === false` → no-op. That's fine in steady state, but
  // during the runScan() promise window, an unrelated rerender could
  // re-enter with stale `scan === null` and fire a SECOND runScan. The
  // current shape uses the closure-captured `scan` exactly once per
  // (open, mode) transition, which is what we want.
  useEffect(() => {
    if (open && mode === 'folder' && !scan && !busy) runScan();
    // eslint-disable-next-line react-hooks/exhaustive-deps -- see comment above.
  }, [open, mode]);

  // Load the browsable destinations the first time the "From destination"
  // tab opens. Same reasoning as the scan effect above — keyed off the
  // user's intent (open + mode), not off internal state (srcDests, busy)
  // that would loop.
  useEffect(() => {
    if (!open || mode !== 'source' || srcDests.length > 0 || busy) return;
    (async () => {
      try {
        const { items } = await api.destinations.list();
        const browsable = items.filter(
          (d) => d.status === 'connected' && BROWSABLE_TYPES.includes(d.type),
        );
        setSrcDests(browsable);
        if (browsable[0]) setSrcDestId(browsable[0].id);
      } catch (e: any) {
        setError(e?.message ?? String(e));
      }
    })();
    // eslint-disable-next-line react-hooks/exhaustive-deps -- see comment above.
  }, [open, mode]);

  if (!open) return null;

  /** Add dropped/picked files to the list, rejecting unrecognized types and dupes. */
  const addFiles = (incoming: File[]) => {
    const bad = incoming.find((f) => !PART_RE.test(f.name));
    if (bad) {
      setError(`"${bad.name}" isn't a backup part. Use .zip, .zip.enc or .sql/.gz database dumps.`);
      return;
    }
    setError(null);
    setFiles((prev) => {
      const seen = new Set(prev.map((f) => `${f.name}:${f.size}`));
      const merged = [...prev];
      for (const f of incoming) {
        const key = `${f.name}:${f.size}`;
        if (!seen.has(key)) { seen.add(key); merged.push(f); }
      }
      return merged;
    });
  };

  const removeFile = (idx: number) =>
    setFiles((prev) => prev.filter((_, i) => i !== idx));

  const upload = async () => {
    if (files.length === 0) return;
    setBusy(true); setError(null); setProgress(0);
    try {
      await api.backups.import(files, setProgress);
      onImported();
      onClose();
    } catch (e: any) {
      setError(e?.message ?? String(e));
      setBusy(false);
    }
  };

  const folderImport = async () => {
    setBusy(true); setError(null);
    try {
      await api.backups.importFolder.commit();
      onImported();
      onClose();
    } catch (e: any) {
      setError(e?.message ?? String(e));
      setBusy(false);
    }
  };

  const runSourceScan = async () => {
    if (!srcDestId) return;
    setBusy(true); setError(null); setSrcBackups(null);
    try {
      const res = await api.backups.importSource.scan(srcDestId);
      setSrcBackups(res.backups);
    } catch (e: any) {
      setError(e?.message ?? String(e));
    } finally {
      setBusy(false);
    }
  };

  const sourceImport = async (extId: string) => {
    setBusy(true); setError(null);
    try {
      await api.backups.importSource.commit(srcDestId, extId);
      onImported();
      onClose();
    } catch (e: any) {
      setError(e?.message ?? String(e));
      setBusy(false);
    }
  };

  const switchMode = (m: Mode) => { if (!busy) { setMode(m); setError(null); } };

  const totalBytes = files.reduce((s, f) => s + f.size, 0);

  return (
    <Card id="backreload-import-section" className="border-brand-200">
      <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">
            <Upload className="h-4 w-4" />
          </span>
          <div className="flex-1">
            <CardTitle>Import a backup</CardTitle>
            <p className="text-xs text-ink-500 mt-0.5">
              Bring in a backup downloaded from BackReload. Include the database
              (<code className="font-mono text-[11px]">db.sql.gz</code>) plus each
              <code className="font-mono text-[11px]"> .zip</code> part. It's reassembled into
              one restorable backup.
            </p>
          </div>
        </div>
        <button
          onClick={() => !busy && onClose()}
          disabled={busy}
          className="h-8 w-8 grid place-items-center rounded-md text-ink-400 hover:text-ink-900 hover:bg-ink-100 disabled:opacity-40 disabled:cursor-not-allowed"
          title={busy ? 'Cannot close while importing' : 'Close'}
          aria-label={busy ? 'Cannot close while importing' : 'Close import'}
        >
          <X className="h-4 w-4" />
        </button>
      </CardHeader>

      <CardBody className="space-y-4">
        {/* Mode switch */}
        <div className="inline-flex rounded-lg border border-ink-200 p-0.5 bg-ink-50">
          {([
            { id: 'upload', label: 'Upload parts',     icon: Upload },
            { id: 'folder', label: 'Server folder',    icon: Server },
            { id: 'source', label: 'From destination', icon: HardDrive },
          ] as const).map(({ id, label, icon: Icon }) => (
            <button
              key={id}
              type="button"
              onClick={() => switchMode(id)}
              disabled={busy}
              className={cn(
                'flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-colors disabled:cursor-not-allowed',
                mode === id ? 'bg-white text-ink-900 shadow-soft' : 'text-ink-500 hover:text-ink-800',
              )}
            >
              <Icon className="h-3.5 w-3.5" />
              {label}
            </button>
          ))}
        </div>

        {/* ─────────────── Upload mode ─────────────── */}
        {mode === 'upload' && (
          <>
            {bk.maxUploadBytes > 0 && !busy && (
              <p className="text-[11px] text-ink-500 leading-relaxed">
                Your host caps a single upload at <strong>{formatBytes(bk.maxUploadBytes)}</strong>.
                Parts are sent in small chunks, so this isn't a ceiling on the import. If a
                firewall keeps blocking, switch to <strong>Server folder</strong>.
              </p>
            )}
            {!busy && (
              <div
                onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
                onDragLeave={() => setDrag(false)}
                onDrop={(e) => {
                  e.preventDefault();
                  setDrag(false);
                  const dropped = Array.from(e.dataTransfer.files);
                  if (dropped.length) addFiles(dropped);
                }}
                onClick={() => inputRef.current?.click()}
                className={cn(
                  'flex flex-col items-center justify-center gap-3 px-6 py-8 rounded-xl border-2 border-dashed cursor-pointer transition-colors',
                  drag ? 'border-brand-500 bg-brand-50' : 'border-ink-200 hover:border-ink-300 hover:bg-ink-50/40',
                )}
              >
                <span className="h-12 w-12 grid place-items-center rounded-2xl bg-brand-50 text-brand-700">
                  <Upload className="h-6 w-6" />
                </span>
                <div className="text-center">
                  <p className="text-sm font-medium text-ink-900">
                    {files.length ? 'Add more parts' : 'Drop backup parts here'}
                  </p>
                  <p className="text-xs text-ink-500 mt-1">
                    or click to browse · select all parts at once · .zip, .zip.enc, .sql.gz
                  </p>
                </div>
                <input
                  ref={inputRef}
                  type="file"
                  multiple
                  accept=".zip,.enc,.gz,.sql,application/zip,application/gzip,application/octet-stream"
                  className="hidden"
                  onChange={(e) => {
                    const picked = Array.from(e.target.files ?? []);
                    if (picked.length) addFiles(picked);
                    e.target.value = '';
                  }}
                />
              </div>
            )}

            {files.length > 0 && (
              <div className="space-y-2">
                <div className="flex items-center justify-between text-xs text-ink-500">
                  <span>{files.length} part{files.length === 1 ? '' : 's'} selected</span>
                  <span className="tabular-nums">{formatBytes(totalBytes)}</span>
                </div>
                <div className="rounded-xl border border-ink-200 divide-y divide-ink-100">
                  {files.map((f, idx) => (
                    <div key={`${f.name}:${f.size}`} className="flex items-center gap-3 p-2.5">
                      <span className="h-9 w-9 grid place-items-center rounded-lg bg-white border border-ink-200 text-ink-700 shrink-0">
                        <FileArchive className="h-4 w-4" />
                      </span>
                      <div className="flex-1 min-w-0">
                        <p className="text-sm font-medium text-ink-900 truncate font-mono">{f.name}</p>
                        <p className="text-[11px] text-ink-500">{formatBytes(f.size)}</p>
                      </div>
                      {!busy && (
                        <button
                          onClick={() => removeFile(idx)}
                          className="h-7 w-7 grid place-items-center rounded-md text-ink-400 hover:text-ink-900 hover:bg-ink-100 shrink-0"
                          title="Remove"
                        >
                          <X className="h-4 w-4" />
                        </button>
                      )}
                    </div>
                  ))}
                </div>
              </div>
            )}

            {busy && (
              <>
                <div>
                  <div className="flex items-center justify-between text-xs text-ink-600 mb-1.5">
                    <span>{progress < 100 ? 'Uploading parts…' : 'Finishing up…'}</span>
                    <span>{progress}%</span>
                  </div>
                  <Progress value={progress} />
                </div>
                <div className="flex items-start gap-2.5 rounded-lg border border-amber-200 bg-amber-50 p-3">
                  <AlertTriangle className="h-4 w-4 text-amber-600 shrink-0 mt-0.5" />
                  <div className="text-xs text-amber-900">
                    <p className="font-medium">Don't close this tab or navigate away.</p>
                    <p className="mt-0.5 text-amber-800">
                      The upload will be cancelled if you do. Large backups upload in many small
                      chunks and may take several minutes. Progress is shown above.
                    </p>
                  </div>
                </div>
              </>
            )}

            {!busy && files.length > 0 && (
              <div className="flex items-start gap-2 text-xs text-ink-500">
                <CheckCircle2 className="h-3.5 w-3.5 text-emerald-600 shrink-0 mt-0.5" />
                <span>
                  Ready to upload. Include every part: the database and all
                  <code className="font-mono"> .zip</code> parts (uploads, plugins, themes, others) downloaded together. If the
                  upload is blocked by a firewall, use <strong>Server folder</strong> instead.
                </span>
              </div>
            )}
          </>
        )}

        {/* ─────────────── Server-folder mode ─────────────── */}
        {mode === 'folder' && (
          <div className="space-y-3">
            <div className="flex items-start gap-2 text-xs text-ink-600 leading-relaxed">
              <Server className="h-3.5 w-3.5 text-brand-600 shrink-0 mt-0.5" />
              <span>
                Copy the backup parts onto this server with <strong>FTP / SFTP / cPanel File
                Manager</strong> into the folder below, then scan. This never touches an HTTP
                upload, so a host firewall can't block it.
              </span>
            </div>

            {scan && (
              <div className="rounded-lg bg-ink-900 text-ink-100 px-3 py-2 font-mono text-[11px] break-all">
                {scan.folder}
              </div>
            )}

            <div className="flex items-center gap-2">
              <Button
                size="sm"
                variant="secondary"
                leftIcon={busy
                  ? <Loader2 className="h-3.5 w-3.5 animate-spin" />
                  : <RefreshCw className="h-3.5 w-3.5" />}
                onClick={runScan}
                disabled={busy}
              >
                {busy ? 'Scanning…' : 'Rescan folder'}
              </Button>
              {scan && (
                <span className="text-[11px] text-ink-500">
                  {scan.parts.length} part{scan.parts.length === 1 ? '' : 's'} found
                </span>
              )}
            </div>

            {scan && scan.parts.length > 0 && (
              <div className="rounded-xl border border-ink-200 divide-y divide-ink-100">
                {scan.parts.map((p) => (
                  <div key={p.name} className="flex items-center gap-3 p-2.5">
                    <span className="h-9 w-9 grid place-items-center rounded-lg bg-white border border-ink-200 text-ink-700 shrink-0">
                      <FileArchive className="h-4 w-4" />
                    </span>
                    <div className="flex-1 min-w-0">
                      <p className="text-sm font-medium text-ink-900 truncate font-mono">{p.name}</p>
                      <p className="text-[11px] text-ink-500">{p.category} · {formatBytes(p.size)}</p>
                    </div>
                    <CheckCircle2 className="h-4 w-4 text-emerald-600 shrink-0" />
                  </div>
                ))}
              </div>
            )}

            {scan && scan.parts.length === 0 && (
              <div className="flex flex-col items-center gap-2 py-6 text-center">
                <FolderSearch className="h-8 w-8 text-ink-300" />
                <p className="text-sm text-ink-600">No backup parts in the folder yet</p>
                <p className="text-[11px] text-ink-400">
                  Copy the parts into the path above, then rescan.
                </p>
              </div>
            )}

            {scan && scan.unknown.length > 0 && (
              <p className="text-[11px] text-amber-700">
                Ignored {scan.unknown.length} unrecognized file{scan.unknown.length === 1 ? '' : 's'}:
                {' '}{scan.unknown.slice(0, 4).join(', ')}{scan.unknown.length > 4 ? '…' : ''}
              </p>
            )}

            {scan && scan.parts.length > 0 && (
              <Button
                variant="primary"
                leftIcon={busy ? <Loader2 className="h-4 w-4 animate-spin" /> : <Server className="h-4 w-4" />}
                onClick={folderImport}
                disabled={busy}
              >
                {busy ? 'Importing…' : `Import ${scan.parts.length} part${scan.parts.length === 1 ? '' : 's'}`}
              </Button>
            )}
          </div>
        )}

        {/* ─────────────── From-destination mode ─────────────── */}
        {mode === 'source' && (
          <div className="space-y-3">
            <div className="flex items-start gap-2 text-xs text-ink-600 leading-relaxed">
              <HardDrive className="h-3.5 w-3.5 text-brand-600 shrink-0 mt-0.5" />
              <span>
                Browse a storage destination for backups, including ones made on another
                site. Importing recreates the backup here, and its files are pulled from the
                destination when you restore. No upload, so no firewall to clear.
              </span>
            </div>

            {srcDests.length === 0 ? (
              <div className="flex flex-col items-center gap-2 py-6 text-center">
                <HardDrive className="h-8 w-8 text-ink-300" />
                <p className="text-sm text-ink-600">No browsable destinations connected</p>
                <p className="text-[11px] text-ink-400">
                  Connect a Local, FTP, SFTP or S3 destination first.
                </p>
              </div>
            ) : (
              <>
                <div className="flex items-center gap-2">
                  <select
                    value={srcDestId}
                    onChange={(e) => { setSrcDestId(e.target.value); setSrcBackups(null); }}
                    disabled={busy}
                    className="flex-1 h-9 px-2.5 text-sm rounded-lg border border-ink-200 bg-white"
                  >
                    {srcDests.map((d) => (
                      <option key={d.id} value={d.id}>{d.name} · {d.type}</option>
                    ))}
                  </select>
                  <Button
                    size="sm"
                    variant="secondary"
                    leftIcon={busy
                      ? <Loader2 className="h-3.5 w-3.5 animate-spin" />
                      : <RefreshCw className="h-3.5 w-3.5" />}
                    onClick={runSourceScan}
                    disabled={busy || !srcDestId}
                  >
                    {busy ? 'Scanning…' : 'Scan'}
                  </Button>
                </div>

                {srcBackups && srcBackups.length > 0 && (
                  <div className="rounded-xl border border-ink-200 divide-y divide-ink-100 max-h-72 overflow-auto">
                    {srcBackups.map((b) => (
                      <div key={b.ext_id} className="flex items-center gap-3 p-2.5">
                        <span className="h-9 w-9 grid place-items-center rounded-lg bg-white border border-ink-200 text-ink-700 shrink-0">
                          <FileArchive className="h-4 w-4" />
                        </span>
                        <div className="flex-1 min-w-0">
                          <p className="text-sm font-medium text-ink-900 truncate">
                            {b.created_at ? formatDateTime(b.created_at) : b.ext_id}
                          </p>
                          <p className="text-[11px] text-ink-500 truncate">
                            {b.type} · {b.parts} part{b.parts === 1 ? '' : 's'} · {formatBytes(b.size)}
                            {b.source_url ? ` · ${b.source_url.replace(/^https?:\/\//, '')}` : ''}
                          </p>
                        </div>
                        {b.already_imported ? (
                          <span className="text-[11px] text-ink-400 shrink-0">Imported</span>
                        ) : (
                          <button
                            type="button"
                            onClick={() => sourceImport(b.ext_id)}
                            disabled={busy}
                            className="text-xs font-medium text-brand-600 hover:text-brand-700 hover:underline shrink-0 disabled:opacity-50"
                          >
                            Import
                          </button>
                        )}
                      </div>
                    ))}
                  </div>
                )}

                {srcBackups && srcBackups.length === 0 && (
                  <div className="flex flex-col items-center gap-2 py-6 text-center">
                    <FolderSearch className="h-8 w-8 text-ink-300" />
                    <p className="text-sm text-ink-600">No backups found in this destination</p>
                    <p className="text-[11px] text-ink-400">
                      Pick another destination, or back this site up to it first.
                    </p>
                  </div>
                )}
              </>
            )}
          </div>
        )}

        {error && (
          <div className="rounded-lg border border-red-200 bg-red-50 p-3 space-y-2">
            <div className="flex items-start gap-2">
              <AlertCircle className="h-4 w-4 text-danger shrink-0 mt-0.5" />
              <p className="text-xs text-red-800 break-words">{error}</p>
            </div>
            {/* When the chunked upload fails with a firewall-shaped error
                (4xx — 406, 403, 413), the upload route is almost certainly
                blocked at the host. Offer a one-click jump to the route that
                doesn't go through HTTP. */}
            {mode === 'upload' && /HTTP\s+4\d\d|406|403|413/i.test(error) && (
              <Button
                size="sm"
                variant="primary"
                leftIcon={<Server className="h-3.5 w-3.5" />}
                onClick={() => switchMode('folder')}
              >
                Switch to Server folder
              </Button>
            )}
          </div>
        )}
      </CardBody>

      <div className="px-5 py-3 border-t border-ink-100 bg-ink-50/50 rounded-b-2xl flex items-center gap-2">
        {mode === 'upload' && (
          <Button variant="primary" leftIcon={<Upload className="h-4 w-4" />}
            disabled={files.length === 0 || busy} onClick={upload}>
            {busy ? `Uploading… ${progress}%` : 'Upload & import'}
          </Button>
        )}
        <Button variant="ghost" onClick={onClose} disabled={busy} className="ml-auto">
          Cancel
        </Button>
      </div>
    </Card>
  );
}
