import type { ChangeEvent, DragEvent } from 'react';
import { useState } from 'react';
import { FileUp, Loader2, X } from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { PRIORITY } from '../shared';
import { useSession, useTorrentMutations } from '../useTransmissionData';

// Add by magnet/URL or by .torrent file, one dialog.
//
// Transmission's torrent-add takes either `filename` (a magnet or an http URL it fetches itself) or
// `metainfo` (base64 of the .torrent bytes). It cannot take both, so the two inputs are exclusive here:
// picking a file clears the text box and vice versa, rather than letting you fill in both and guess.

type AddTorrentDialogProps = { open: boolean; onOpenChange: (open: boolean) => void };

type PickedFile = { name: string; metainfo: string };

/** FileReader gives back a `data:...;base64,XXX` URL; torrent-add wants only the payload. */
async function readAsBase64(file: File): Promise<string> {
  const buffer = await file.arrayBuffer();
  let binary = '';
  const bytes = new Uint8Array(buffer);
  // Chunked because String.fromCharCode(...bytes) blows the argument limit on a torrent with many pieces.
  for (let i = 0; i < bytes.length; i += 8192) {
    binary += String.fromCharCode(...bytes.subarray(i, i + 8192));
  }
  return btoa(binary);
}

export const AddTorrentDialog = ({ open, onOpenChange }: AddTorrentDialogProps) => {
  const { session } = useSession();
  const { add } = useTorrentMutations();

  const [urls, setUrls] = useState('');
  const [files, setFiles] = useState<PickedFile[]>([]);
  const [downloadDir, setDownloadDir] = useState('');
  const [labels, setLabels] = useState('');
  const [paused, setPaused] = useState(false);
  const [priority, setPriority] = useState('0');
  const [sequential, setSequential] = useState(false);
  const [dragging, setDragging] = useState(false);
  const [busy, setBusy] = useState(false);

  const defaultDir = (session?.['download-dir'] as string) ?? '';

  const reset = () => {
    setUrls('');
    setFiles([]);
    setDownloadDir('');
    setLabels('');
    setPaused(false);
    setPriority('0');
    setSequential(false);
  };

  const close = () => {
    reset();
    onOpenChange(false);
  };

  const takeFiles = async (list: FileList | null) => {
    if (!list?.length) return;
    const picked = await Promise.all([...list].map(async (f) => ({ name: f.name, metainfo: await readAsBase64(f) })));
    setFiles((prev) => [...prev, ...picked]);
    setUrls('');
  };

  const onDrop = (ev: DragEvent) => {
    ev.preventDefault();
    setDragging(false);
    void takeFiles(ev.dataTransfer.files);
  };

  const onPick = (ev: ChangeEvent<HTMLInputElement>) => {
    void takeFiles(ev.target.files);
    // Clear the input so picking the same file twice still fires a change event.
    ev.target.value = '';
  };

  const urlList = urls
    .split('\n')
    .map((s) => s.trim())
    .filter(Boolean);

  const canSubmit = (files.length > 0 || urlList.length > 0) && !busy;

  const submit = async () => {
    const common = {
      downloadDir: downloadDir.trim() || undefined,
      labels: labels
        .split(',')
        .map((l) => l.trim())
        .filter(Boolean),
      paused,
      bandwidthPriority: Number(priority),
      sequentialDownload: sequential,
    };

    setBusy(true);
    let added = 0;
    let duplicate = 0;
    let failed = 0;

    // Sequential rather than Promise.all: torrent-add is not idempotent and the daemon serialises it
    // anyway, and a partial failure should report which of a paste of twenty magnets got in.
    for (const file of files) {
      const result = await add.mutateAsync({ ...common, metainfo: file.metainfo }).catch(() => null);
      if (!result) failed += 1;
      else if (result.status === 'duplicate') duplicate += 1;
      else added += 1;
    }
    for (const url of urlList) {
      const result = await add.mutateAsync({ ...common, filename: url }).catch(() => null);
      if (!result) failed += 1;
      else if (result.status === 'duplicate') duplicate += 1;
      else added += 1;
    }
    setBusy(false);

    const parts = [added && `${added} added`, duplicate && `${duplicate} already present`, failed && `${failed} failed`]
      .filter(Boolean)
      .join(', ');
    if (added || duplicate) toast.success(parts);
    else if (failed) toast.error(parts);

    if (!failed) close();
  };

  return (
    <Dialog open={open} onOpenChange={(next) => (next ? onOpenChange(true) : close())}>
      <DialogContent className="max-w-lg">
        <DialogHeader>
          <DialogTitle>Add torrents</DialogTitle>
          <DialogDescription>Paste magnet links or drop .torrent files.</DialogDescription>
        </DialogHeader>

        <div className="space-y-4">
          <div className="space-y-1.5">
            <Label htmlFor="tr-add-urls">Magnet links or URLs</Label>
            <Textarea
              id="tr-add-urls"
              rows={3}
              value={urls}
              disabled={files.length > 0}
              placeholder={'magnet:?xt=urn:btih:…\nhttps://example.org/file.torrent'}
              onChange={(ev) => setUrls(ev.target.value)}
              className="font-mono text-xs"
            />
            <p className="text-xs text-muted-foreground">One per line.</p>
          </div>

          <div
            onDragOver={(ev) => {
              ev.preventDefault();
              setDragging(true);
            }}
            onDragLeave={() => setDragging(false)}
            onDrop={onDrop}
            className={`rounded-lg border border-dashed p-4 text-center transition-colors ${
              dragging ? 'border-primary bg-primary/5' : 'border-border'
            }`}
          >
            <FileUp className="mx-auto h-5 w-5 text-muted-foreground" />
            <p className="mt-1.5 text-xs text-muted-foreground">
              Drop .torrent files here, or{' '}
              <label className="cursor-pointer text-primary underline">
                browse
                <input type="file" accept=".torrent" multiple className="hidden" onChange={onPick} />
              </label>
            </p>
            {files.length > 0 && (
              <ul className="mt-2 space-y-1 text-left">
                {files.map((f, i) => (
                  <li key={`${f.name}-${i}`} className="flex items-center gap-2 rounded bg-muted px-2 py-1 text-xs">
                    <span className="min-w-0 flex-1 truncate">{f.name}</span>
                    <button
                      type="button"
                      onClick={() => setFiles((prev) => prev.filter((_, idx) => idx !== i))}
                      className="text-muted-foreground hover:text-foreground"
                      aria-label={`Remove ${f.name}`}
                    >
                      <X className="h-3 w-3" />
                    </button>
                  </li>
                ))}
              </ul>
            )}
          </div>

          <div className="grid grid-cols-2 gap-3">
            <div className="col-span-2 space-y-1.5">
              <Label htmlFor="tr-add-dir">Download to</Label>
              <Input
                id="tr-add-dir"
                value={downloadDir}
                placeholder={defaultDir || 'server default'}
                onChange={(ev) => setDownloadDir(ev.target.value)}
              />
            </div>
            <div className="space-y-1.5">
              <Label htmlFor="tr-add-labels">Labels</Label>
              <Input
                id="tr-add-labels"
                value={labels}
                placeholder="comma, separated"
                onChange={(ev) => setLabels(ev.target.value)}
              />
            </div>
            <div className="space-y-1.5">
              <Label htmlFor="tr-add-priority">Priority</Label>
              <Select value={priority} onValueChange={setPriority}>
                <SelectTrigger id="tr-add-priority">
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  <SelectItem value={String(PRIORITY.High)}>High</SelectItem>
                  <SelectItem value={String(PRIORITY.Normal)}>Normal</SelectItem>
                  <SelectItem value={String(PRIORITY.Low)}>Low</SelectItem>
                </SelectContent>
              </Select>
            </div>
          </div>

          <div className="flex gap-6">
            <label className="flex items-center gap-2 text-sm">
              <Checkbox checked={paused} onCheckedChange={(v) => setPaused(v === true)} />
              Start paused
            </label>
            <label className="flex items-center gap-2 text-sm">
              <Checkbox checked={sequential} onCheckedChange={(v) => setSequential(v === true)} />
              Sequential download
            </label>
          </div>
        </div>

        <DialogFooter>
          <Button variant="ghost" onClick={close} disabled={busy}>
            Cancel
          </Button>
          <Button onClick={submit} disabled={!canSubmit}>
            {busy && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
            Add {files.length + urlList.length || ''}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
};
