import type { Services } from '@learnitive/context';
import { WORKSPACE_STORAGE_TYPE } from '@learnitive/constants';
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, toast } from '@learnitive/ui-components';
import { Download, FolderPlus, RefreshCcw } from 'lucide-react';
import React from 'react';
import { downloadLinuxTerminalWorkspace, type LinuxTerminalSession } from '../saas/saas-api';
import { synchronizeSandboxProject } from '../saas/sandbox-project-sync';
import {
  listTerminalProjectLinks,
  loadWorkspaceIntoTerminal,
  syncWorkspaceBack,
  type TerminalProjectLink,
  type TerminalSyncConflict,
} from '../saas/terminal-workspace-sync';

type WorkspaceRow = { name: string; type: string; bytes: number; available: boolean; reason?: string };
type PendingDelete = { link: TerminalProjectLink; paths: string[] };
type ConflictRow = { project: string; conflict: TerminalSyncConflict };

function formatBytes(bytes: number): string {
  if (bytes >= 1_000_000_000) return `${(bytes / 1_000_000_000).toFixed(1)} GB`;
  if (bytes >= 1_000_000) return `${(bytes / 1_000_000).toFixed(1)} MB`;
  if (bytes >= 1_000) return `${Math.round(bytes / 1000)} KB`;
  return `${bytes} B`;
}

function saveBlob(blob: Blob, filename: string): void {
  const url = URL.createObjectURL(blob);
  const anchor = document.createElement('a');
  anchor.href = url;
  anchor.download = filename;
  document.body.append(anchor);
  anchor.click();
  anchor.remove();
  setTimeout(() => URL.revokeObjectURL(url), 10_000);
}

export function TerminalProjectControls({
  core,
  session,
  onWorkspaceChanged,
  showLoadProjects = true,
  attachedProject,
}: {
  core: Services;
  session: LinuxTerminalSession;
  onWorkspaceChanged?: () => void;
  showLoadProjects?: boolean;
  attachedProject?: { wsName: string; projectId: string };
}) {
  const [loadOpen, setLoadOpen] = React.useState(false);
  const [rows, setRows] = React.useState<WorkspaceRow[]>([]);
  const [selected, setSelected] = React.useState<Set<string>>(new Set());
  const [busy, setBusy] = React.useState(false);
  const [progress, setProgress] = React.useState('');
  const [pendingDeletes, setPendingDeletes] = React.useState<PendingDelete[]>([]);
  const [conflicts, setConflicts] = React.useState<ConflictRow[]>([]);

  const discover = React.useCallback(async () => {
    setBusy(true);
    try {
      const workspaces = (await core.workspaceOps.getAllWorkspaces()).filter((workspace) => !workspace.deleted && [
        WORKSPACE_STORAGE_TYPE.Browser,
        WORKSPACE_STORAGE_TYPE.BrowserOPFS,
        WORKSPACE_STORAGE_TYPE.NativeFS,
      ].includes(workspace.type as never));
      const next: WorkspaceRow[] = [];
      for (const workspace of workspaces) {
        try {
          const provider = await core.projectStorage.getProvider(workspace.name);
          const entries = await provider.listEntries();
          const bytes = entries.reduce((sum, entry) => sum + (entry.kind === 'file' ? Number(entry.size || 0) : 0), 0);
          next.push({ name: workspace.name, type: workspace.type, bytes, available: true });
        } catch (error) {
          next.push({ name: workspace.name, type: workspace.type, bytes: 0, available: false, reason: error instanceof Error ? error.message : 'Permission required' });
        }
      }
      setRows(next);
      setSelected(new Set(next.filter((row) => row.available).map((row) => row.name)));
    } finally {
      setBusy(false);
    }
  }, [core.projectStorage, core.workspaceOps]);

  React.useEffect(() => {
    if (loadOpen) void discover();
  }, [discover, loadOpen]);

  const loadSelected = React.useCallback(async () => {
    if (!selected.size) return;
    setBusy(true);
    let totalFiles = 0;
    let totalBytes = 0;
    try {
      for (const name of selected) {
        setProgress(`Loading ${name}…`);
        const result = await loadWorkspaceIntoTerminal(core, session, name, ({ path, overallUploaded, overallTotal }) => {
          setProgress(`${name}: ${path} · ${formatBytes(overallUploaded)} / ${formatBytes(overallTotal)}`);
        });
        totalFiles += result.uploadedFiles;
        totalBytes += result.uploadedBytes;
      }
      toast.success(`Loaded ${selected.size} project${selected.size === 1 ? '' : 's'} · ${totalFiles} files · ${formatBytes(totalBytes)}.`);
      setLoadOpen(false);
      onWorkspaceChanged?.();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not load projects into the terminal.');
    } finally {
      setProgress('');
      setBusy(false);
    }
  }, [core, onWorkspaceChanged, selected, session]);

  const syncLinks = React.useCallback(async (confirmRemoteDeletions = false, only?: TerminalProjectLink[]) => {
    const links = only || listTerminalProjectLinks(session);
    if (!links.length) {
      toast.info('Load a Browser or external project before using Sync Back.');
      return;
    }
    setBusy(true);
    setPendingDeletes([]);
    setConflicts([]);
    const deletes: PendingDelete[] = [];
    const conflictRows: ConflictRow[] = [];
    let downloaded = 0, uploaded = 0, deletedLocal = 0, deletedRemote = 0;
    try {
      for (const link of links) {
        setProgress(`Synchronizing ${link.wsName}…`);
        const result = await syncWorkspaceBack(core, session, link.wsName, { confirmRemoteDeletions });
        downloaded += result.downloaded;
        uploaded += result.uploaded;
        deletedLocal += result.deletedLocal;
        deletedRemote += result.deletedRemote;
        if (result.deletionCandidates.length) deletes.push({ link, paths: result.deletionCandidates });
        for (const conflict of result.conflicts) conflictRows.push({ project: link.wsName, conflict });
      }
      if (deletes.length && !confirmRemoteDeletions) setPendingDeletes(deletes);
      if (conflictRows.length) setConflicts(conflictRows);
      const changed = downloaded + uploaded + deletedLocal + deletedRemote;
      if (!deletes.length && !conflictRows.length) toast.success(changed ? `Synchronized ${changed} project file change${changed === 1 ? '' : 's'}.` : 'Projects are already synchronized.');
      else if (conflictRows.length) toast.warning(`${conflictRows.length} synchronization conflict${conflictRows.length === 1 ? '' : 's'} require review.`);
      onWorkspaceChanged?.();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not synchronize terminal projects.');
    } finally {
      setProgress('');
      setBusy(false);
    }
  }, [core, onWorkspaceChanged, session]);

  const syncAttachedProject = React.useCallback(async () => {
    if (!attachedProject) return;
    setBusy(true);
    setPendingDeletes([]);
    setConflicts([]);
    setProgress(`Synchronizing ${attachedProject.wsName}…`);
    try {
      const result = await synchronizeSandboxProject(core, attachedProject.wsName, attachedProject.projectId);
      if (result.conflicts.length) {
        toast.warning(`${result.conflicts.length} project file conflict${result.conflicts.length === 1 ? '' : 's'} require review.`);
      } else {
        const changed = result.uploaded + result.downloaded + result.deletedRemote;
        toast.success(changed ? `Synchronized ${changed} project file change${changed === 1 ? '' : 's'}.` : 'Project is already synchronized.');
      }
      onWorkspaceChanged?.();
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not synchronize the attached project.');
    } finally {
      setProgress('');
      setBusy(false);
    }
  }, [attachedProject, core, onWorkspaceChanged]);

  const syncBack = React.useCallback(async () => {
    if (attachedProject) {
      await syncAttachedProject();
      return;
    }
    await syncLinks(false);
  }, [attachedProject, syncAttachedProject, syncLinks]);

  const confirmDeletes = React.useCallback(async () => {
    const links = pendingDeletes.map((entry) => entry.link);
    setPendingDeletes([]);
    await syncLinks(true, links);
  }, [pendingDeletes, syncLinks]);

  const downloadAll = React.useCallback(async () => {
    setBusy(true);
    setProgress('Preparing workspace ZIP…');
    try {
      const blob = await downloadLinuxTerminalWorkspace(session.sessionId);
      saveBlob(blob, 'learnitive-workspace.zip');
    } catch (error) {
      toast.error(error instanceof Error ? error.message : 'Could not download the terminal workspace.');
    } finally {
      setProgress('');
      setBusy(false);
    }
  }, [session.sessionId]);

  return (
    <>
      {showLoadProjects ? <Button size="sm" variant="ghost" className="h-7 px-2 text-[11px] text-inherit" disabled={busy} onClick={() => setLoadOpen(true)}>
        <FolderPlus className="mr-1 size-3.5" />Load Projects
      </Button> : null}
      <Button size="sm" variant="ghost" className="h-7 px-2 text-[11px] text-inherit" disabled={busy} onClick={() => void syncBack()}>
        <RefreshCcw className="mr-1 size-3.5" />Sync Back
      </Button>
      <Button size="icon" variant="ghost" className="size-7 text-inherit" disabled={busy} aria-label="Download all terminal project files" title="Download all loaded project files as ZIP" onClick={() => void downloadAll()}>
        <Download className="size-3.5" />
      </Button>
      {progress && <span className="max-w-72 truncate text-[10px] text-[#fce94f]" title={progress}>{progress}</span>}

      <Dialog open={showLoadProjects && loadOpen} onOpenChange={setLoadOpen}>
        <DialogContent className="sm:max-w-xl">
          <DialogHeader>
            <DialogTitle>Load Projects</DialogTitle>
            <DialogDescription>Copy selected Browser or external projects into this temporary Linux workspace. Reconstructable dependency/build folders are skipped.</DialogDescription>
          </DialogHeader>
          <div className="max-h-[50vh] space-y-1 overflow-auto rounded border p-2">
            {busy && !rows.length && <p className="p-3 text-sm text-muted-foreground">Reading projects…</p>}
            {!busy && !rows.length && <p className="p-3 text-sm text-muted-foreground">No Browser or external-folder projects are available on this device.</p>}
            {rows.map((row) => (
              <label key={row.name} className={`flex items-center gap-3 rounded px-2 py-2 text-sm ${row.available ? 'hover:bg-muted/50' : 'opacity-60'}`}>
                <input type="checkbox" checked={selected.has(row.name)} disabled={!row.available || busy} onChange={(event) => { const checked = event.currentTarget.checked; setSelected((current) => { const next = new Set(current); if (checked) next.add(row.name); else next.delete(row.name); return next; }); }} />
                <span className="min-w-0 flex-1"><strong className="block truncate">{row.name}</strong><span className="text-xs text-muted-foreground">{row.type === WORKSPACE_STORAGE_TYPE.NativeFS ? 'Local folder' : 'Browser project'}{row.reason ? ` · ${row.reason}` : ''}</span></span>
                <span className="text-xs tabular-nums text-muted-foreground">{row.available ? formatBytes(row.bytes) : 'Unavailable'}</span>
              </label>
            ))}
          </div>
          {progress && <p className="truncate text-xs text-muted-foreground" title={progress}>{progress}</p>}
          <DialogFooter><Button variant="secondary" onClick={() => setLoadOpen(false)} disabled={busy}>Cancel</Button><Button onClick={() => void loadSelected()} disabled={busy || !selected.size}>Load selected</Button></DialogFooter>
        </DialogContent>
      </Dialog>

      <Dialog open={pendingDeletes.length > 0} onOpenChange={(open) => { if (!open) setPendingDeletes([]); }}>
        <DialogContent className="sm:max-w-lg">
          <DialogHeader><DialogTitle>Confirm source deletions</DialogTitle><DialogDescription>These files were deleted in the temporary cloud workspace and are unchanged in your browser/local source. Sync Back can delete them from the authoritative source.</DialogDescription></DialogHeader>
          <div className="max-h-72 overflow-auto rounded border p-2 text-sm">{pendingDeletes.flatMap((entry) => entry.paths.map((path) => <div key={`${entry.link.wsName}:${path}`} className="py-1"><strong>{entry.link.wsName}</strong> · {path}</div>))}</div>
          <DialogFooter><Button variant="secondary" onClick={() => setPendingDeletes([])}>Keep source files</Button><Button variant="destructive" onClick={() => void confirmDeletes()}>Apply deletions</Button></DialogFooter>
        </DialogContent>
      </Dialog>

      <Dialog open={conflicts.length > 0} onOpenChange={(open) => { if (!open) setConflicts([]); }}>
        <DialogContent className="sm:max-w-lg">
          <DialogHeader><DialogTitle>Synchronization conflicts</DialogTitle><DialogDescription>Both the temporary cloud copy and the authoritative browser/local source changed. Learnitive did not overwrite either version.</DialogDescription></DialogHeader>
          <div className="max-h-72 overflow-auto rounded border p-2 text-sm">{conflicts.map(({ project, conflict }) => <div key={`${project}:${conflict.path}`} className="py-1"><strong>{project}</strong> · {conflict.path}</div>)}</div>
          <DialogFooter><Button onClick={() => setConflicts([])}>Close</Button></DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
}
