import { useState, useEffect, useRef, useCallback } from 'react';
import { useParams, Link } from 'react-router';
import { Loader2, CheckCircle2, XCircle, Ban, Clock, ArrowLeft, Square } from 'lucide-react';
import { useClient } from 'hooks/useClient';
import { toast } from 'sonner';
import { PhaseBar, type DownloadProgress } from './DownloadJobDetail';

type ScriptJob = {
  id: string;
  mode: string;
  taskDirName: string;
  taskName: string;
  status: 'pending' | 'running' | 'completed' | 'failed' | 'stopped' | 'interrupted';
  exitCode: number | null;
  error: string | null;
  progress: DownloadProgress | null;
  createdAt: string;
  startedAt: string | null;
  completedAt: string | null;
};

const LOG_POLL_MS = 1500;
const isTerminal = (s: string) => s === 'completed' || s === 'failed' || s === 'stopped' || s === 'interrupted';

// A script may publish counter-style progress via the `@@officer:progress@@` sentinel (e.g. the
// download-media capability). When shaped like that, render the two phase bars above the log.
const isDownloadProgress = (p: unknown): p is DownloadProgress =>
  !!p && typeof p === 'object' && 'meta' in p && 'dl' in p;

const statusBadge = (status: string, exitCode: number | null) => {
  switch (status) {
    case 'running':
      return {
        icon: <Loader2 className="h-4 w-4 animate-spin" />,
        label: 'Running',
        cls: 'text-amber-600 dark:text-amber-500',
      };
    case 'pending':
      return { icon: <Clock className="h-4 w-4" />, label: 'Queued', cls: 'text-blue-600 dark:text-blue-400' };
    case 'completed':
      return { icon: <CheckCircle2 className="h-4 w-4" />, label: 'Completed', cls: 'text-duck-teal' };
    case 'failed':
      return {
        icon: <XCircle className="h-4 w-4" />,
        label: `Failed${exitCode != null ? ` (exit ${exitCode})` : ''}`,
        cls: 'text-red-600 dark:text-red-400',
      };
    case 'stopped':
      return { icon: <Ban className="h-4 w-4" />, label: 'Stopped', cls: 'text-duck-dark/50 dark:text-foreground/50' };
    default:
      return {
        icon: <XCircle className="h-4 w-4" />,
        label: 'Interrupted',
        cls: 'text-orange-600 dark:text-orange-400',
      };
  }
};

export const ScriptJobDetail = () => {
  const { id } = useParams<{ id: string }>();
  const client = useClient();

  const [job, setJob] = useState<ScriptJob | null>(null);
  const [output, setOutput] = useState('');
  const [loading, setLoading] = useState(true);
  const offsetRef = useRef(0);
  const preRef = useRef<HTMLPreElement | null>(null);
  const autoFollow = useRef(true);

  // Append the next slice of the log from the tracked offset.
  const pullLog = useCallback(async () => {
    if (!id) return;
    try {
      const res = await client.get<{ text: string; offset: number; size: number }>(
        `/jobs/${id}/log?offset=${offsetRef.current}`,
      );
      if (res.text) {
        offsetRef.current = res.offset;
        setOutput((prev) => prev + res.text);
      }
    } catch {
      // transient
    }
  }, [id]);

  // Initial load: job + full log so far.
  useEffect(() => {
    if (!id) return;
    let cancelled = false;
    (async () => {
      try {
        const j = await client.get<ScriptJob>(`/jobs/${id}`);
        if (cancelled) return;
        setJob(j);
        await pullLog();
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [id]);

  // While live, poll the log + status until the job reaches a terminal state.
  useEffect(() => {
    if (!id || !job || isTerminal(job.status)) return;
    const timer = setInterval(async () => {
      await pullLog();
      try {
        const j = await client.get<ScriptJob>(`/jobs/${id}`);
        setJob(j);
        if (isTerminal(j.status)) {
          clearInterval(timer);
          await pullLog(); // final catch-up
        }
      } catch {
        // transient
      }
    }, LOG_POLL_MS);
    return () => clearInterval(timer);
  }, [id, job?.status]);

  // Auto-scroll to the bottom unless the user scrolled up.
  useEffect(() => {
    const el = preRef.current;
    if (el && autoFollow.current) el.scrollTop = el.scrollHeight;
  }, [output]);

  const onScroll = () => {
    const el = preRef.current;
    if (!el) return;
    autoFollow.current = el.scrollHeight - el.scrollTop - el.clientHeight < 40;
  };

  const stop = async () => {
    if (!id) return;
    try {
      await client.post(`/jobs/${id}/stop`, {});
      toast.success('Stop requested');
    } catch {
      toast.error('Failed to stop');
    }
  };

  if (loading) {
    return (
      <div className="flex-1 flex items-center justify-center text-duck-dark/50 dark:text-foreground/50">
        <Loader2 className="h-5 w-5 animate-spin" />
      </div>
    );
  }
  if (!job) {
    return (
      <div className="flex-1 flex flex-col items-center justify-center gap-3 text-duck-dark/60 dark:text-foreground/60">
        <span>Job not found.</span>
        <Link to="/jobs" className="text-duck-teal hover:underline">
          Back to jobs
        </Link>
      </div>
    );
  }

  const badge = statusBadge(job.status, job.exitCode);
  const running = !isTerminal(job.status);

  return (
    <div className="flex-1 flex flex-col min-h-0">
      <div className="px-5 py-3 border-b border-duck-dark/10 flex items-center gap-3">
        <Link
          to="/jobs"
          className="text-duck-dark/50 dark:text-foreground/50 hover:text-duck-dark dark:hover:text-foreground"
        >
          <ArrowLeft className="h-4 w-4" />
        </Link>
        <div className="flex flex-col min-w-0">
          <span className="text-sm font-medium text-duck-dark dark:text-foreground truncate">{job.taskName}</span>
          <span className="text-xs text-duck-dark/40 dark:text-foreground/40 truncate">{job.taskDirName}</span>
        </div>
        <span className={`ml-auto flex items-center gap-1.5 text-sm ${badge.cls}`}>
          {badge.icon} {badge.label}
        </span>
        {running && (
          <button
            onClick={stop}
            className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-red-500/10 text-red-600 dark:text-red-400 hover:bg-red-500/20 cursor-pointer transition-colors"
          >
            <Square className="h-3 w-3" /> Stop
          </button>
        )}
      </div>

      {isDownloadProgress(job.progress) && (
        <div className="px-5 py-4 border-b border-duck-dark/10 flex flex-col gap-4">
          <PhaseBar
            label="Titles"
            c={job.progress.meta}
            active={job.progress.phase === 'metadata'}
            savedLabel="found"
            failedLabel="skipped"
          />
          <PhaseBar
            label="Download"
            c={job.progress.dl}
            active={job.progress.phase === 'download'}
            savedLabel="saved"
            failedLabel="failed"
          />
          {running && job.progress.phase === 'download' && job.progress.current && (
            <div className="truncate text-xs text-duck-dark/50 dark:text-foreground/50" title={job.progress.current}>
              <Loader2 className="mr-1.5 inline h-3 w-3 animate-spin" />
              {job.progress.current}
            </div>
          )}
        </div>
      )}

      <pre
        ref={preRef}
        onScroll={onScroll}
        className="flex-1 min-h-0 overflow-auto m-0 p-4 text-xs font-mono leading-relaxed text-duck-dark/80 dark:text-foreground/80 bg-duck-dark/[0.03] dark:bg-foreground/[0.03] whitespace-pre-wrap break-words"
      >
        {output || (running ? '…' : '(no output)')}
      </pre>

      {job.error && !running && (
        <div className="px-5 py-2 border-t border-duck-dark/10 text-xs text-red-600 dark:text-red-400">{job.error}</div>
      )}
    </div>
  );
};
