import { useEffect, useState } from 'react';
import { Volume2, Info, Scan, RefreshCw, Globe, Copy, ChevronDown, ChevronRight, Trash2, Users, Sparkles, Radio } from 'lucide-react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { usePlaybackStore } from '../store/usePlaybackStore';
import { PageContainer } from '../components/PageContainer';
import { PageHeader } from '../components/PageHeader';
import api from '../services/api';
import { settingsService, type EnrichSettingKey, type EnrichSettings } from '../services/settingsService';
import { STORAGE_KEYS, readStoredVolume } from '../lib/theme';
import { MAX_CROSSFADE_MS, PREFETCH_LEAD_SECONDS } from '../lib/playbackPrefs';
import { toast } from '../store/useToastStore';
import type { Track } from '../types';

const ENRICH_LABELS: Record<EnrichSettingKey, { label: string; desc: string }> = {
  enrich_metadata: { label: 'Album info', desc: 'MusicBrainz ID, Discogs ID, release year' },
  enrich_genres: { label: 'Genre tags', desc: 'Last.fm & MusicBrainz tags → genres' },
  enrich_cover_art: { label: 'Cover art', desc: 'Discogs + Cover Art Archive images' },
  enrich_lyrics: { label: 'Lyrics', desc: 'LRCLib synced & plain-text lyrics' },
  enrich_artist_similarity: { label: 'Similar artists', desc: 'Last.fm similar-artist recommendations' },
  enrich_audio_analysis: { label: 'Audio analysis', desc: 'BPM & key from embedded tags; energy from ReplayGain' },
};

function EnrichToggles() {
  const [settings, setSettings] = useState<EnrichSettings | null>(null);
  const [saving, setSaving] = useState<EnrichSettingKey | null>(null);
  const [loadState, setLoadState] = useState<'loading' | 'ready' | 'error'>('loading');
  const [updateError, setUpdateError] = useState<string | null>(null);

  const loadSettings = async () => {
    setLoadState('loading');
    setUpdateError(null);
    try {
      setSettings(await settingsService.load());
      setLoadState('ready');
    } catch {
      setLoadState('error');
    }
  };

  useEffect(() => {
    void loadSettings();
  }, []);

  const toggle = async (key: EnrichSettingKey) => {
    if (!settings || saving) return;
    const next = !settings[key];
    setSaving(key);
    setUpdateError(null);
    // Optimistic update.
    setSettings((prev) => prev ? { ...prev, [key]: next } : prev);
    try {
      await settingsService.update(key, next);
    } catch {
      // Revert on failure.
      setSettings((prev) => prev ? { ...prev, [key]: !next } : prev);
      setUpdateError(`Could not update ${ENRICH_LABELS[key].label}. Your previous setting was restored.`);
    } finally {
      setSaving(null);
    }
  };

  if (loadState === 'loading') {
    return <p className="pt-2 text-xs text-muted" role="status">Loading enrichment settings…</p>;
  }

  if (loadState === 'error' || !settings) {
    return (
      <div className="flex items-center justify-between gap-3 border-t border-border pt-3">
        <p className="text-xs text-red-400" role="alert">Couldn&apos;t load enrichment settings.</p>
        <button type="button" onClick={() => void loadSettings()} className="rounded-lg border border-border px-3 py-1.5 text-xs text-text hover:bg-surface1">
          Retry
        </button>
      </div>
    );
  }

  const enabledCount = Object.values(settings).filter(Boolean).length;

  return (
    <div className="space-y-3 pt-2 border-t border-border">
      <h3 className="text-sm font-medium text-muted flex items-center gap-2">
        <Globe size={14} />Enrichment
      </h3>
      <p className="text-xs text-muted/70 -mt-1">
        Which external services to query during library scan. Changes apply to
        the <strong>next scan</strong>.
      </p>
      <p className="text-xs text-muted" role="status" aria-live="polite">
        {saving ? `Saving ${ENRICH_LABELS[saving].label}…` : `${enabledCount} of ${Object.keys(ENRICH_LABELS).length} enrichment jobs enabled.`}
      </p>
      {updateError && <p className="text-xs text-red-400" role="alert">{updateError}</p>}
      {Object.entries(ENRICH_LABELS).map(([key, { label, desc }]) => {
        const k = key as EnrichSettingKey;
        const on = settings[k];
        return (
          <button type="button" key={k} onClick={() => void toggle(k)} disabled={saving !== null}
            role="switch" aria-checked={on}
            className="w-full flex items-center justify-between gap-3 rounded-lg border border-border px-3 py-2 text-left transition-colors hover:bg-surface1 disabled:opacity-50">
            <div className="min-w-0">
              <div className="text-sm font-medium text-text">{label}</div>
              <div className="text-xs text-muted truncate">{desc}</div>
            </div>
            <div className={`relative h-[20px] w-[36px] shrink-0 rounded-full transition-colors ${on ? 'bg-accent' : 'bg-surface2'}`}>
              <div className={`absolute left-[2px] top-[2px] h-[16px] w-[16px] rounded-full transition-transform ${on ? 'bg-on-accent translate-x-[16px]' : 'bg-secondary'}`} />
            </div>
          </button>
        );
      })}
    </div>
  );
}

function AdminAction({
  icon: Icon,
  label,
  busyLabel = 'Working…',
  doneLabel = 'Done',
  errorLabel = 'Failed',
  action,
  successMsg,
  errorMsg,
}: {
  icon: typeof Scan;
  label: string;
  busyLabel?: string;
  doneLabel?: string;
  errorLabel?: string;
  action: () => Promise<unknown>;
  successMsg: string;
  errorMsg: string;
}) {
  const [state, setState] = useState<'idle' | 'loading' | 'done' | 'error'>('idle');

  const handleClick = async () => {
    setState('loading');
    try {
      await action();
      setState('done');
      toast.success(successMsg);
      setTimeout(() => setState('idle'), 3000);
    } catch {
      setState('error');
      toast.error(errorMsg);
      setTimeout(() => setState('idle'), 3000);
    }
  };

  return (
    <button
      type="button"
      onClick={handleClick}
      disabled={state === 'loading'}
      className={`admin-btn ${state === 'done' ? 'admin-btn--done' : state === 'error' ? 'admin-btn--error' : ''}`}
      aria-live="polite"
    >
      <Icon size={16} className={state === 'loading' ? 'animate-spin' : ''} />
      {state === 'loading' ? busyLabel
        : state === 'done' ? doneLabel
        : state === 'error' ? errorLabel
        : label}
    </button>
  );
}

type DupMode = 'hash' | 'title-artist';

interface DupGroup {
  key: string;
  tracks: Track[];
}

const DUP_MODE_LABELS: Record<DupMode, { label: string; desc: string }> = {
  hash: { label: 'Same file', desc: 'Byte-identical copies (same content hash)' },
  'title-artist': { label: 'Same track', desc: 'Same title + artist, different files' },
};

function DuplicatesSection() {
  const qc = useQueryClient();
  const [mode, setMode] = useState<DupMode>('hash');
  const [expanded, setExpanded] = useState<string | null>(null);

  const { data, isLoading, isError, refetch } = useQuery<DupGroup[]>({
    queryKey: ['duplicates', mode],
    queryFn: () => api.get('/admin/duplicates', { params: { mode } }).then((r) => r.data),
    enabled: false,
  });

  const merge = useMutation({
    mutationFn: ({ keepId, deleteIds }: { keepId: string; deleteIds: string[] }) =>
      api.post('/admin/duplicates/merge', { keepId, deleteIds }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: ['duplicates', mode] });
      qc.invalidateQueries({ queryKey: ['tracks'] });
      refetch();
      toast.success('Duplicate merged. File deleted from disk.');
    },
    onError: () => toast.error('Could not merge the duplicate. Try again.'),
  });

  const groups = data ?? [];

  return (
    <div className="space-y-3 pt-2 border-t border-border">
      <div className="flex items-center justify-between">
        <h3 className="text-sm font-medium text-muted flex items-center gap-2">
          <Copy size={14} /> Duplicates
        </h3>
        <button onClick={() => refetch()}
          disabled={isLoading}
          className="text-xs px-3 py-1.5 rounded-lg border border-border text-text hover:bg-surface1 disabled:opacity-50">
          {isLoading ? 'Scanning…' : 'Scan'}
        </button>
      </div>

      {/* Mode tabs */}
      <div className="flex gap-1 rounded-lg bg-surface1 p-1">
        {(Object.keys(DUP_MODE_LABELS) as DupMode[]).map((m) => (
          <button key={m} onClick={() => { setMode(m); setExpanded(null); }}
            className={`flex-1 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${
              mode === m ? 'bg-surface0 text-text shadow-sm' : 'text-muted hover:text-text'
            }`}>
            {DUP_MODE_LABELS[m].label}
          </button>
        ))}
      </div>
      <p className="text-xs text-muted/70 -mt-1">{DUP_MODE_LABELS[mode].desc}</p>

      {isError && <p className="text-xs text-red-400">Scan failed.</p>}
      {data && groups.length === 0 && (
        <p className="text-sm text-muted">No duplicates found.</p>
      )}
      {groups.map((group) => {
        const isOpen = expanded === group.key;
        const [keep, ...rest] = group.tracks;
        const label = mode === 'hash' ? keep.title : group.key;
        return (
          <div key={group.key} className="rounded-lg border border-border overflow-hidden">
            <button onClick={() => setExpanded(isOpen ? null : group.key)}
              className="w-full flex items-center gap-3 px-4 py-3 text-left hover:bg-surface1">
              {isOpen ? <ChevronDown size={14} className="text-muted flex-none" /> : <ChevronRight size={14} className="text-muted flex-none" />}
              <span className="text-sm font-medium text-text flex-1 truncate">{label}</span>
              <span className="text-xs text-muted flex-none">{group.tracks.length} copies</span>
            </button>
            {isOpen && (
              <div className="border-t border-border divide-y divide-line">
                {group.tracks.map((t, i) => (
                  <div key={t.id} className="flex items-start gap-3 px-4 py-2.5">
                    <div className="flex-1 min-w-0">
                      <div className="text-xs text-text truncate">{t.path}</div>
                      <div className="text-xs text-muted">{t.artist} · {t.play_count} plays{i === 0 ? ' · kept by default' : ''}</div>
                    </div>
                    {i > 0 && (
                      <button
                        onClick={() => merge.mutate({ keepId: keep.id, deleteIds: [t.id] })}
                        disabled={merge.isPending}
                        title="Delete this duplicate"
                        className="flex-none text-red-400 hover:text-red-300 disabled:opacity-50 p-1">
                        <Trash2 size={14} />
                      </button>
                    )}
                  </div>
                ))}
                <div className="px-4 py-2">
                  <button
                    onClick={() => merge.mutate({ keepId: keep.id, deleteIds: rest.map((t) => t.id) })}
                    disabled={merge.isPending || rest.length === 0}
                    className="text-xs px-3 py-1.5 rounded-lg border border-red-500/40 text-red-400 hover:bg-red-500/10 disabled:opacity-50">
                    Delete all duplicates, keep most-played
                  </button>
                </div>
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

export default function Settings() {
  const volume = usePlaybackStore((s) => s.volume);
  const setVolume = usePlaybackStore((s) => s.setVolume);
  const prefetchNext = usePlaybackStore((s) => s.prefetchNext);
  const setPrefetchNext = usePlaybackStore((s) => s.setPrefetchNext);
  const crossfadeMs = usePlaybackStore((s) => s.crossfadeMs);
  const setCrossfadeMs = usePlaybackStore((s) => s.setCrossfadeMs);

  useEffect(() => {
    setVolume(readStoredVolume(volume));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const handleVolume = (v: number) => {
    setVolume(v);
    try { localStorage.setItem(STORAGE_KEYS.volume, String(v)); } catch { /**/ }
  };

  return (
    <PageContainer width="sm" className="space-y-6 lg:max-w-3xl">
      <PageHeader title="Settings" subtitle="Preferences are stored locally in this browser." />

      <section className="rounded-xl border border-border bg-surface0/50 p-3 space-y-3">
        <h2 className="flex items-center gap-2 text-sm font-semibold text-text"><Volume2 size={15} className="text-accent" />Default volume</h2>
        <div className="flex items-center gap-4">
          <input type="range" min={0} max={1} step={0.01} value={volume}
            onChange={(e) => handleVolume(Number(e.target.value))}
            className="flex-1" aria-label="Volume" />
          <span className="w-12 flex-none text-right font-mono text-xs tabular-nums text-machine">{Math.round(volume * 100)}%</span>
        </div>
      </section>

      <section className="rounded-xl border border-border bg-surface0/50 p-3 space-y-3">
        <h2 className="flex items-center gap-2 text-sm font-semibold text-text"><Radio size={15} className="text-accent" />Transitions</h2>

        <button type="button" onClick={() => setPrefetchNext(!prefetchNext)}
          role="switch" aria-checked={prefetchNext}
          className="w-full flex items-center justify-between gap-3 rounded-lg border border-border px-3 py-2 text-left transition-colors hover:bg-surface1">
          <div className="min-w-0">
            <div className="text-sm font-medium text-text">Preload next track</div>
            <div className="text-xs text-muted">Buffers the upcoming track {PREFETCH_LEAD_SECONDS}s before the current one ends</div>
          </div>
          <div className={`relative h-[20px] w-[36px] shrink-0 rounded-full transition-colors ${prefetchNext ? 'bg-accent' : 'bg-surface2'}`}>
            <div className={`absolute left-[2px] top-[2px] h-[16px] w-[16px] rounded-full transition-transform ${prefetchNext ? 'bg-on-accent translate-x-[16px]' : 'bg-secondary'}`} />
          </div>
        </button>

        <div className="space-y-2">
          <label htmlFor="crossfade" className="text-sm font-medium text-text">Crossfade</label>
          <div className="flex items-center gap-4">
            <input id="crossfade" type="range" min={0} max={MAX_CROSSFADE_MS} step={100} value={crossfadeMs}
              onChange={(e) => setCrossfadeMs(Number(e.target.value))}
              className="flex-1" />
            <span className="w-12 flex-none text-right font-mono text-xs tabular-nums text-machine">
              {crossfadeMs === 0 ? 'Off' : `${(crossfadeMs / 1000).toFixed(1)}s`}
            </span>
          </div>
          <p className="text-xs text-muted/70">
            Overlaps the end of one track with the start of the next. Off still
            joins tracks without a pause — the next one is resolved and loaded
            during the last second, then starts the moment this one ends.
          </p>
        </div>
      </section>

      <section className="rounded-xl border border-border bg-surface0/50 p-3 space-y-3">
        <h2 className="flex items-center gap-2 text-sm font-semibold text-text"><Scan size={15} className="text-accent" />Library</h2>

        <div className="flex flex-wrap gap-3">
          <AdminAction icon={Scan} label="Scan library" busyLabel="Scanning…" doneLabel="Scan enqueued"
            action={() => api.post('/admin/scan', { directory: '/music' })}
            successMsg="Library scan enqueued. Track progress in Jobs."
            errorMsg="Could not start scan. Check that the backend is running." />
          <AdminAction icon={RefreshCw} label="Reindex search" busyLabel="Indexing…" doneLabel="Index enqueued"
            action={() => api.post('/admin/reindex-tracks')}
            successMsg="Search reindex enqueued."
            errorMsg="Could not start reindex." />
          <AdminAction icon={Users} label="Reprocess artists" busyLabel="Enqueuing…" doneLabel="Reprocess enqueued"
            action={() => api.post('/admin/reprocess-artists')}
            successMsg="Artist reprocess enqueued. Runs in the background."
            errorMsg="Could not start artist reprocess." />
          <AdminAction icon={Sparkles} label="Re-enrich metadata" busyLabel="Enqueuing…" doneLabel="Re-enrich enqueued"
            action={async () => {
              const res = await api.post('/admin/reenrich-tracks');
              const count = res.data?.trackCount ?? 0;
              toast.success(`Re-enrich enqueued for ${count} tracks. Runs in the background.`);
            }}
            successMsg="Re-enrich enqueued."
            errorMsg="Could not start re-enrich." />
        </div>
        <p className="text-xs text-muted/70 -mt-1">
          <strong>Reprocess artists</strong> re-resolves canonical names, MBIDs and
          images for every artist, and merges duplicates. <strong>Re-enrich metadata</strong>{' '}
          re-queries MusicBrainz/Discogs for all tracks (album titles, years, cover
          art) without re-scanning files. Both run in the background.
        </p>

        <EnrichToggles />
        <DuplicatesSection />
      </section>

      <section className="rounded-xl border border-border bg-surface0/50 p-3 space-y-3">
        <h2 className="flex items-center gap-2 text-sm font-semibold text-text"><Info size={15} className="text-accent" />About</h2>
        <dl className="space-y-2 text-sm">
          <div className="flex justify-between"><dt className="text-muted">Application</dt><dd className="text-text font-medium">muzick</dd></div>
          <div className="flex justify-between"><dt className="text-muted">Version</dt><dd className="text-text font-medium tabular-nums">0.1.0</dd></div>
          <div className="flex justify-between gap-4"><dt className="text-muted">API base</dt><dd className="font-mono text-xs text-text break-all">{api.defaults.baseURL ?? '/api'}</dd></div>
        </dl>
      </section>
    </PageContainer>
  );
}
