import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next';
import { Badge, Button, Callout, ScrollArea, Spinner } from '@olly/modern-sk';
import {
  buildUploadFormData,
  useUploadTrackMutation,
} from '../../api/endpoints/upload';
import { useGetTrackQuery } from '../../api/endpoints/library';
import { MetadataStatusBadge } from '../../components/track/MetadataStatusBadge';

/** Pure client-side state — this is a transient upload queue, never server data. */
type ItemStatus = 'queued' | 'uploading' | 'done' | 'duplicate' | 'error';

interface QueueItem {
  id: string;
  file: File;
  status: ItemStatus;
  error?: string;
  trackId?: string;
}

/** The endpoint takes one file per request, so we cap concurrent requests. */
const MAX_CONCURRENCY = 3;

function extractError(err: unknown): string {
  if (typeof err === 'object' && err !== null) {
    const e = err as { data?: { message?: string; detail?: string }; error?: string };
    return e.data?.message ?? e.data?.detail ?? e.error ?? 'Upload failed';
  }
  return 'Upload failed';
}

/** `/upload` — A8 drag-and-drop upload of own files. */
export function UploadPage() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const [uploadTrack] = useUploadTrackMutation();

  const [items, setItems] = useState<QueueItem[]>([]);
  const [dragging, setDragging] = useState(false);

  const inputRef = useRef<HTMLInputElement>(null);
  const idCounter = useRef(0);
  const activeCount = useRef(0);
  const pending = useRef<QueueItem[]>([]);

  const patchItem = (id: string, patch: Partial<QueueItem>) =>
    setItems((prev) => prev.map((it) => (it.id === id ? { ...it, ...patch } : it)));

  // Ref-based concurrency pump: refs (not state) so it is safe to call from
  // async callbacks without stale closures over the queue.
  const pump = () => {
    while (activeCount.current < MAX_CONCURRENCY && pending.current.length > 0) {
      const item = pending.current.shift()!;
      activeCount.current += 1;
      patchItem(item.id, { status: 'uploading', error: undefined });
      uploadTrack(buildUploadFormData(item.file))
        .unwrap()
        .then((res) => {
          patchItem(item.id, {
            status: res.already_exists ? 'duplicate' : 'done',
            trackId: res.track_id,
          });
        })
        .catch((err: unknown) => {
          patchItem(item.id, { status: 'error', error: extractError(err) });
        })
        .finally(() => {
          activeCount.current -= 1;
          pump();
        });
    }
  };

  const addFiles = (files: File[]) => {
    if (files.length === 0) return;
    const newItems: QueueItem[] = files.map((file) => ({
      id: `u${idCounter.current++}`,
      file,
      status: 'queued',
    }));
    setItems((prev) => [...prev, ...newItems]);
    pending.current.push(...newItems);
    pump();
  };

  const retry = (id: string) => {
    let target: QueueItem | undefined;
    setItems((prev) => {
      target = prev.find((it) => it.id === id);
      return prev.map((it) =>
        it.id === id ? { ...it, status: 'queued', error: undefined } : it,
      );
    });
    if (target) {
      pending.current.push({ ...target, status: 'queued', error: undefined });
      pump();
    }
  };

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files) addFiles(Array.from(e.target.files));
    e.target.value = ''; // allow re-selecting the same file
  };

  const onDrop = (e: React.DragEvent) => {
    e.preventDefault();
    setDragging(false);
    if (e.dataTransfer.files) addFiles(Array.from(e.dataTransfer.files));
  };

  const completed = items.filter(
    (it) => it.status === 'done' || it.status === 'duplicate',
  ).length;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      <div
        style={{
          padding: '1.25rem 1.5rem',
          borderBottom: '1px solid var(--color-border)',
          flexShrink: 0,
        }}
      >
        <h2 style={{ margin: 0, fontSize: '1.125rem', fontWeight: 700 }}>
          {t('upload.title')}
        </h2>
      </div>

      <ScrollArea style={{ flex: 1 }}>
        <div
          style={{
            padding: '1.5rem',
            display: 'flex',
            flexDirection: 'column',
            gap: '1.25rem',
          }}
        >
          <div
            onClick={() => inputRef.current?.click()}
            onDragEnter={(e) => {
              e.preventDefault();
              setDragging(true);
            }}
            onDragOver={(e) => e.preventDefault()}
            onDragLeave={() => setDragging(false)}
            onDrop={onDrop}
            style={{
              border: `2px dashed ${
                dragging ? 'var(--color-accent)' : 'var(--color-border)'
              }`,
              borderRadius: 10,
              padding: '2.5rem 1.5rem',
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'center',
              gap: '0.75rem',
              cursor: 'pointer',
              background: dragging ? 'var(--color-surface-2)' : 'transparent',
              transition: 'background 120ms, border-color 120ms',
            }}
          >
            <div style={{ fontSize: '2rem' }}>⬆</div>
            <div style={{ fontWeight: 600 }}>{t('upload.dropzone.title')}</div>
            <div style={{ fontSize: '0.8125rem', color: 'var(--color-text-3)' }}>
              {t('upload.dropzone.hint')}
            </div>
            <Button variant="primary" size="sm" type="button">
              {t('upload.dropzone.button')}
            </Button>
            <input
              ref={inputRef}
              type="file"
              multiple
              accept="audio/*"
              onChange={onInputChange}
              style={{ display: 'none' }}
            />
          </div>

          <Callout variant="info">{t('upload.metadataPending')}</Callout>

          {items.length > 0 && (
            <div
              style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}
            >
              <div
                style={{
                  display: 'flex',
                  justifyContent: 'space-between',
                  alignItems: 'center',
                }}
              >
                <span style={{ fontWeight: 600, fontSize: '0.875rem' }}>
                  {t('upload.queueTitle', {
                    completed,
                    total: items.length,
                  })}
                </span>
                {completed > 0 && (
                  <Button
                    variant="ghost"
                    size="sm"
                    type="button"
                    onClick={() =>
                      setItems((prev) =>
                        prev.filter(
                          (it) =>
                            it.status !== 'done' && it.status !== 'duplicate',
                        ),
                      )
                    }
                  >
                    {t('upload.clearCompleted')}
                  </Button>
                )}
              </div>
              {items.map((item) => (
                <UploadRow
                  key={item.id}
                  item={item}
                  onRetry={() => retry(item.id)}
                  onEditMetadata={() =>
                    void navigate(`/tracks/${item.trackId}/metadata`)
                  }
                />
              ))}
            </div>
          )}
        </div>
      </ScrollArea>
    </div>
  );
}

function UploadRow({
  item,
  onRetry,
  onEditMetadata,
}: {
  item: QueueItem;
  onRetry: () => void;
  onEditMetadata: () => void;
}) {
  const { t } = useTranslation();
  const done = item.status === 'done' || item.status === 'duplicate';
  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: '0.75rem',
        padding: '0.625rem 0.75rem',
        border: '1px solid var(--color-border)',
        borderRadius: 8,
        background: 'var(--color-surface-1)',
      }}
    >
      <div style={{ flex: 1, minWidth: 0 }}>
        <div
          style={{
            fontSize: '0.875rem',
            fontWeight: 500,
            overflow: 'hidden',
            textOverflow: 'ellipsis',
            whiteSpace: 'nowrap',
          }}
          title={item.file.name}
        >
          {item.file.name}
        </div>
        {item.status === 'error' && item.error && (
          <div style={{ fontSize: '0.75rem', color: 'var(--color-text-3)' }}>
            {item.error}
          </div>
        )}
        {done && item.trackId && <EnrichmentStatus trackId={item.trackId} />}
      </div>

      <StatusBadge status={item.status} />

      {item.status === 'error' && (
        <Button variant="ghost" size="sm" type="button" onClick={onRetry}>
          {t('upload.retry')}
        </Button>
      )}
      {done && item.trackId && (
        <Button
          variant="ghost"
          size="sm"
          type="button"
          onClick={onEditMetadata}
        >
          {t('upload.editMetadata')}
        </Button>
      )}
    </div>
  );
}

/**
 * Polls a just-uploaded track until enrichment settles, then shows the outcome.
 * Metadata enrichment runs asynchronously in a worker after the upload response
 * returns, so without polling the row would never reflect the resolved title/
 * artist or a failure reason. Polling stops (interval → 0) once the status
 * leaves `pending`.
 */
function EnrichmentStatus({ trackId }: { trackId: string }) {
  const { t } = useTranslation();
  const [pollMs, setPollMs] = useState(2500);
  const { data } = useGetTrackQuery(trackId, { pollingInterval: pollMs });

  useEffect(() => {
    if (data && data.metadataStatus !== 'pending') setPollMs(0);
  }, [data]);

  const status = data?.metadataStatus ?? 'pending';
  const resolved =
    data && data.metadataStatus === 'enriched'
      ? `${data.artistName} · ${data.title}`
      : t(`metadata.statusHint.${status}`);

  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: '0.5rem',
        marginTop: '0.25rem',
      }}
    >
      <MetadataStatusBadge
        status={status}
        error={data?.metadataError}
        hideWhenEnriched={false}
      />
      <span
        style={{
          fontSize: '0.75rem',
          color: 'var(--color-text-3)',
          overflow: 'hidden',
          textOverflow: 'ellipsis',
          whiteSpace: 'nowrap',
        }}
      >
        {status === 'failed' && data?.metadataError
          ? data.metadataError
          : resolved}
      </span>
    </div>
  );
}

function StatusBadge({ status }: { status: ItemStatus }) {
  const { t } = useTranslation();
  if (status === 'uploading') {
    return (
      <Badge variant="neutral">
        <Spinner size="sm" /> {t('upload.status.uploading')}
      </Badge>
    );
  }
  const cfg: Record<
    Exclude<ItemStatus, 'uploading'>,
    { variant: 'lime' | 'ember' | 'neutral' | 'outline'; key: string }
  > = {
    queued: { variant: 'neutral', key: 'upload.status.queued' },
    done: { variant: 'lime', key: 'upload.status.done' },
    duplicate: { variant: 'outline', key: 'upload.status.duplicate' },
    error: { variant: 'ember', key: 'upload.status.error' },
  };
  const { variant, key } = cfg[status];
  return (
    <Badge variant={variant} dot>
      {t(key)}
    </Badge>
  );
}
