import { useEffect, useState, useRef, useCallback } from 'react';
import { useParams, Link } from 'react-router-dom';
import { api, apiRaw } from '../api/client';
import type { Job, LogLine, SyncPair } from '../api/client';
import { Badge } from '@/components/ui/Badge';
import { Button } from '@/components/ui/Button';
import { Switch } from '@/components/ui/Switch';
import { Card } from '@/components/ui/Card';
import { Spinner } from '@/components/ui/Spinner';
import { CopyButton } from '@/components/ui/CopyButton';
import { Textarea } from '@/components/ui/Textarea';
import { Label } from '@/components/ui/Label';
import {
  Modal,
  ModalContent,
  ModalHeader,
  ModalTitle,
  ModalDescription,
  ModalBody,
  ModalFooter,
} from '@/components/ui/Modal';
import { ErrorDetailsModal } from '@/components/ErrorDetailsModal';
import {
  ArrowLeft,
  Download,
  XCircle,
  ScrollText,
  Terminal,
  AlertCircle,
  Ban,
  ChevronDown,
  Activity,
} from 'lucide-react';
import { toast } from 'sonner';
import { statusVariant, statusLabel, getErrorCodeInfo } from '@/lib/status';
import { formatDuration } from '@/lib/utils';
import { cn } from '@/lib/utils';

interface SSEProgress {
  file_bytes: number;
  pct: number;
  speed_bps: number;
  eta_seconds: number;
  xfr_done: number;
  xfr_total: number;
}

interface SSEEvent {
  type: string;
  job_id: number;
  status?: string;
  line?: string;
  stream?: string;
  progress?: SSEProgress;
  totalBytes?: number;
  sentBytes?: number;
}

export default function JobDetail() {
  const { id } = useParams<{ id: string }>();
  const [job, setJob] = useState<Job | null>(null);
  const [pair, setPair] = useState<SyncPair | null>(null);
  const [logs, setLogs] = useState<LogLine[]>([]);
  const [liveLines, setLiveLines] = useState<{ stream: string; text: string }[]>([]);
  const [autoScroll, setAutoScroll] = useState(true);
  const logEndRef = useRef<HTMLDivElement>(null);
  const esRef = useRef<EventSource | null>(null);
  const jobId = Number(id);
  const [loading, setLoading] = useState(true);
  const [cancelModal, setCancelModal] = useState(false);
  const [cancelReason, setCancelReason] = useState('');
  const [errorModal, setErrorModal] = useState(false);
  const [progress, setProgress] = useState<SSEProgress | null>(null);
  const [finalTotals, setFinalTotals] = useState<{ totalBytes: number; sentBytes: number } | null>(null);
  const [hasMoreLogs, setHasMoreLogs] = useState(false);
  const [loadingMore, setLoadingMore] = useState(false);

  useEffect(() => {
    loadJob();
    if (jobId) {
      loadLogs(0);
      const es = new EventSource(
        `/api/jobs/${jobId}/log/stream?job_id=${jobId}`
      );
      esRef.current = es;
      es.onmessage = (e) => {
        const evt: SSEEvent = JSON.parse(e.data);
        if (evt.type === 'log') {
          setLiveLines(prev => [...prev, { stream: evt.stream!, text: evt.line! }]);
        }
        if (evt.type === 'status') {
          setJob(prev => {
            if (!prev) return prev;
            const updated = { ...prev, status: evt.status! };
            if (evt.line) updated.error_message = evt.line;
            return updated;
          });
        }
        if (evt.type === 'progress' && evt.progress) {
          setProgress(evt.progress);
        }
        if (evt.type === 'progress_total' && evt.totalBytes !== undefined && evt.sentBytes !== undefined) {
          setFinalTotals({ totalBytes: evt.totalBytes, sentBytes: evt.sentBytes });
          setProgress(null);
        }
      };
    }
    return () => esRef.current?.close();
  }, [id]);

  useEffect(() => {
    if (autoScroll && logEndRef.current) {
      logEndRef.current.scrollIntoView({ behavior: 'smooth' });
    }
  }, [liveLines, autoScroll]);

  async function loadJob() {
    try {
      const j = await api<Job>(`/api/jobs/${id}`);
      setJob(j);
      const pairs = (await api<SyncPair[]>('/api/sync-pairs')) ?? [];
      const p = pairs.find((sp: SyncPair) => sp.id === j.sync_pair_id);
      setPair(p || null);
    } catch {
    } finally {
      setLoading(false);
    }
  }

  async function loadLogs(offset: number) {
    try {
      const ls = (await api<LogLine[]>(
        `/api/jobs/${id}/log?offset=${offset}&limit=1000`
      )) ?? [];
      if (offset === 0) {
        setLogs(ls);
        setHasMoreLogs(ls.length === 1000);
      } else {
        setLogs(prev => [...prev, ...ls]);
        setHasMoreLogs(ls.length === 1000);
      }
    } catch {}
  }

  async function cancel() {
    try {
      await api(`/api/jobs/${id}/cancel`, {
        method: 'POST',
        body: { reason: cancelReason.trim() || undefined },
      });
      toast.success('Job cancelled');
      setCancelModal(false);
      setCancelReason('');
      loadJob();
    } catch (e: unknown) {
      toast.error((e as Error).message);
    }
  }

async function downloadLog() {
    try {
      const resp = await apiRaw(`/api/jobs/${id}/log/download`);
      if (resp.ok && resp.body) {
        const blob = await resp.blob();
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `job-${id}.log`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
      } else {
        fallbackDownload();
      }
    } catch {
      fallbackDownload();
    }
  }

  function fallbackDownload() {
    const allLines = [
      ...logs.map(l => `[${l.timestamp}] [${l.stream}] ${l.content}`),
      ...liveLines.map(l => `[LIVE] [${l.stream}] ${l.text}`),
    ];
    const blob = new Blob(allLines as string[], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `job-${id}.log`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }

  const copyLog = useCallback(() => {
    const text = [
      ...logs.map(l => `[${l.timestamp}] [${l.stream}] ${l.content}`),
      ...liveLines.map(l => `[LIVE] [${l.stream}] ${l.text}`),
    ].join('\n');
    navigator.clipboard.writeText(text);
    toast.success('Log copied to clipboard');
  }, [logs, liveLines]);

  if (loading) {
    return (
      <div className="flex items-center justify-center min-h-[50vh]">
        <Spinner size="lg" />
      </div>
    );
  }

  if (!job) {
    return (
      <div className="flex flex-col items-center justify-center min-h-[50vh] gap-3">
        <p className="text-fg-muted">Job not found</p>
        <Button variant="secondary" asChild>
          <Link to="/jobs">
            <ArrowLeft className="h-4 w-4" />
            Back to Jobs
          </Link>
        </Button>
      </div>
    );
  }

  const totalLines = logs.length + liveLines.length;

  const fullErrorLog = [
    ...logs.filter(l => l.stream === 'stderr').map(l => l.content),
    ...liveLines.filter(l => l.stream === 'stderr').map(l => l.text),
  ].join('\n');

  const errorInfo = getErrorCodeInfo(job.error_code ?? null);

  return (
    <div className="space-y-4">
      <div className="flex items-center gap-3">
        <Button variant="ghost" size="sm" asChild>
          <Link to="/jobs">
            <ArrowLeft className="h-4 w-4" />
            Job History
          </Link>
        </Button>
        <div className="flex items-center gap-2">
          <h1 className="text-xl font-bold text-fg font-mono">#{job.id}</h1>
          <Badge variant={statusVariant(job.status)} label={statusLabel(job.status)} />
        </div>
      </div>

      <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
        {[
          { label: 'Sync Pair', value: pair?.name || `Pair ${job.sync_pair_id}` },
          { label: 'Trigger', value: job.trigger_type },
          {
            label: 'Duration',
            value: job.started_at
              ? formatDuration(
                  (job.finished_at
                    ? new Date(job.finished_at).getTime()
                    : Date.now()) -
                    new Date(job.started_at).getTime()
                )
              : '-',
          },
          {
            label: 'Started',
            value: job.started_at
              ? new Date(job.started_at).toLocaleString()
              : '-',
          },
        ].map(item => (
          <Card key={item.label}>
            <div className="p-4">
              <div className="text-xs font-medium text-fg-muted uppercase tracking-wider mb-1">
                {item.label}
              </div>
              <div className="text-sm font-semibold text-fg truncate">
                {item.value}
              </div>
            </div>
          </Card>
        ))}
      </div>

      {(job.status === 'failed' || (job.status === 'cancelled' && job.error_message)) && (
        <div className={cn(
          "rounded-card border p-4 space-y-2",
          job.status === 'failed'
            ? "border-rose-500/30 bg-rose-500/10"
            : "border-zinc-500/30 bg-zinc-500/10"
        )}>
          <div className="flex items-start gap-2">
            {job.status === 'failed'
              ? <AlertCircle className="h-4 w-4 text-rose-400 shrink-0 mt-0.5" />
              : <Ban className="h-4 w-4 text-zinc-400 shrink-0 mt-0.5" />
            }
            <div className="flex-1 min-w-0">
              <div className="flex items-center gap-2 flex-wrap">
                <span className={cn(
                  "text-sm font-semibold",
                  job.status === 'failed' ? "text-rose-300" : "text-zinc-300"
                )}>
                  {errorInfo?.title ?? (job.status === 'failed' ? 'Job failed' : 'Job cancelled')}
                </span>
                {job.error_code && (
                  <span className={cn(
                    "text-xs font-mono",
                    job.status === 'failed' ? "text-rose-400/60" : "text-zinc-400/60"
                  )}>
                    [{job.error_code}]
                  </span>
                )}
              </div>
              {errorInfo?.hint && (
                <p className={cn(
                  "text-xs mt-0.5",
                  job.status === 'failed' ? "text-rose-400/70" : "text-zinc-400/70"
                )}>
                  {errorInfo.hint}
                </p>
              )}
              <p className={cn(
                "text-xs font-mono mt-1 truncate max-w-2xl",
                job.status === 'failed' ? "text-rose-300/80" : "text-zinc-300/80"
              )}>
                {job.error_message}
              </p>
            </div>
            <Button
              variant="secondary"
              size="sm"
              onClick={() => setErrorModal(true)}
              className={cn(
                "shrink-0 border",
                job.status === 'failed'
                  ? "text-rose-300 hover:text-rose-200 border-rose-500/40 hover:border-rose-400/60"
                  : "text-zinc-300 hover:text-zinc-200 border-zinc-500/40 hover:border-zinc-400/60"
              )}
            >
              {job.status === 'failed' ? <AlertCircle className="h-4 w-4" /> : <Ban className="h-4 w-4" />}
              View error
            </Button>
          </div>
        </div>
      )}

      {['queued', 'waking_up', 'running'].includes(job.status) && (
        <div className="flex items-center gap-3">
          <Button
            variant="danger"
            size="sm"
            onClick={() => setCancelModal(true)}
          >
            <XCircle className="h-4 w-4" />
            Cancel Job
          </Button>
          <div className="flex items-center gap-2 text-sm text-fg-muted">
            <Switch
              id="auto-scroll"
              checked={autoScroll}
              onCheckedChange={setAutoScroll}
            />
            <label htmlFor="auto-scroll" className="cursor-pointer">
              Auto-scroll
            </label>
          </div>
        </div>
      )}

      {(progress || finalTotals) && (
        <TransferProgress progress={progress} finalTotals={finalTotals} />
      )}

      <Card className="flex flex-col min-h-0">
        <div className="flex items-center justify-between px-4 py-3 border-b border-border shrink-0">
          <div className="flex items-center gap-2">
            <Terminal className="h-4 w-4 text-fg-subtle" />
            <span className="text-xs font-semibold text-fg-muted uppercase tracking-wider">
              Output
            </span>
            <span className="text-xs text-fg-subtle">
              {totalLines} line{totalLines !== 1 ? 's' : ''}
            </span>
          </div>
          <div className="flex items-center gap-1">
            <CopyButton
              text={[
                ...logs.map(l => `[${l.timestamp}] [${l.stream}] ${l.content}`),
                ...liveLines.map(l => `[LIVE] [${l.stream}] ${l.text}`),
              ].join('\n')}
              displayText="Copy log"
            />
            <Button variant="ghost" size="icon-sm" onClick={downloadLog} title="Download log">
              <Download className="h-3.5 w-3.5" />
            </Button>
          </div>
        </div>
        <div className="flex-1 overflow-y-auto p-4 font-mono text-xs space-y-0.5 scrollbar-thin min-h-[300px] max-h-[60vh]">
          {logs.length === 0 && liveLines.length === 0 ? (
            <div className="flex flex-col items-center justify-center h-full text-fg-subtle gap-2">
              <ScrollText className="h-6 w-6" />
              <p>No log output yet</p>
            </div>
          ) : (
            <>
              {logs.map(l => (
                <LogLine
                  key={l.id}
                  stream={l.stream}
                  content={l.content}
                  timestamp={l.timestamp}
                />
              ))}
              {liveLines.map((l, i) => (
                <div
                  key={`live-${i}`}
                  className={cn(
                    'flex gap-2',
                    l.stream === 'stderr'
                      ? 'text-rose-400'
                      : 'text-fg-muted'
                  )}
                >
                  <span className="text-accent shrink-0">LIVE</span>
                  <span className="break-all">{l.text}</span>
                </div>
              ))}
            </>
          )}
          <div ref={logEndRef} />
          {hasMoreLogs && (
            <div className="flex justify-center py-2">
              <Button
                variant="secondary"
                size="sm"
                onClick={async () => {
                  setLoadingMore(true);
                  await loadLogs(logs.length);
                  setLoadingMore(false);
                }}
                disabled={loadingMore}
              >
                <ChevronDown className="h-4 w-4" />
                {loadingMore ? 'Loading...' : 'Load more'}
              </Button>
            </div>
          )}
        </div>
      </Card>

      <Modal open={cancelModal} onOpenChange={setCancelModal}>
        <ModalContent size="sm">
          <ModalHeader>
            <ModalTitle>Cancel Job</ModalTitle>
            <ModalDescription>
              Are you sure you want to cancel job #{job.id}? This action cannot
              be undone.
            </ModalDescription>
          </ModalHeader>
          <form onSubmit={e => { e.preventDefault(); cancel(); }}>
            <ModalBody>
              <div className="space-y-1.5">
                <Label htmlFor="cancel-reason">Reason (optional)</Label>
                <Textarea
                  id="cancel-reason"
                  value={cancelReason}
                  onChange={e => setCancelReason(e.target.value)}
                  placeholder="e.g. wrong path, machine offline..."
                  rows={2}
                  className="font-mono text-xs"
                />
                <p className="text-xs text-fg-subtle">
                  Adding a reason helps track why jobs are cancelled
                </p>
              </div>
            </ModalBody>
            <ModalFooter>
              <Button variant="secondary" type="button" onClick={() => { setCancelModal(false); setCancelReason(''); }}>
                Keep Running
              </Button>
              <Button variant="danger-solid" type="submit">
                Cancel Job
              </Button>
            </ModalFooter>
          </form>
        </ModalContent>
      </Modal>

      <ErrorDetailsModal
        open={errorModal}
        onOpenChange={setErrorModal}
        job={job}
        fullLog={fullErrorLog}
      />
    </div>
  );
}

function LogLine({
  stream,
  content,
  timestamp,
}: {
  stream: string;
  content: string;
  timestamp: string;
}) {
  const d = new Date(timestamp);
  const timeStr = `${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}:${d.getSeconds().toString().padStart(2, '0')}`;
  return (
    <div
      className={cn(
        'flex gap-2',
        stream === 'stderr' ? 'text-rose-400' : 'text-fg-muted'
      )}
    >
      <span className="text-fg-subtle shrink-0">{timeStr}</span>
      <span className="break-all">{content}</span>
    </div>
  );
}

function formatBytes(bytes: number): string {
  if (bytes === 0) return '0 B';
  const units = ['B', 'kB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(1024));
  return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${units[i]}`;
}

function formatSpeed(bps: number): string {
  if (bps === 0) return '0 B/s';
  const units = ['B/s', 'kB/s', 'MB/s', 'GB/s'];
  const i = Math.floor(Math.log(bps) / Math.log(1000));
  return `${(bps / Math.pow(1000, i)).toFixed(1)} ${units[i]}`;
}

function TransferProgress({
  progress,
  finalTotals,
}: {
  progress: SSEProgress | null;
  finalTotals: { totalBytes: number; sentBytes: number } | null;
}) {
  const globalPct = finalTotals && finalTotals.totalBytes > 0
    ? Math.round((finalTotals.sentBytes / finalTotals.totalBytes) * 100)
    : null;

  return (
    <Card>
      <div className="p-4 space-y-3">
        <div className="flex items-center gap-2">
          <Activity className="h-4 w-4 text-accent" />
          <span className="text-xs font-semibold text-fg-muted uppercase tracking-wider">
            Transfer Progress
          </span>
        </div>

        {progress && (
          <>
            <div className="space-y-1.5">
              <div className="flex justify-between text-xs text-fg-muted">
                <span>Current file</span>
                <span>{progress.pct}%</span>
              </div>
              <div className="h-2 bg-border rounded-full overflow-hidden">
                <div
                  className="h-full bg-accent transition-all duration-300 rounded-full"
                  style={{ width: `${progress.pct}%` }}
                />
              </div>
            </div>

            <div className="flex justify-between text-xs text-fg-muted">
              <span className="font-mono">
                xfr#{(progress.xfr_done).toLocaleString()}/{progress.xfr_total > 0 ? progress.xfr_total.toLocaleString() : '?'}
              </span>
              <span className="font-mono">
                {formatSpeed(progress.speed_bps)}
              </span>
              <span className="font-mono">
                ETA {progress.eta_seconds > 0 ? `${Math.floor(progress.eta_seconds / 60)}m ${progress.eta_seconds % 60}s` : '-'}
              </span>
            </div>
          </>
        )}

        {finalTotals && (
          <div className="space-y-1.5">
            <div className="flex justify-between text-xs text-fg-muted">
              <span>Total transferred</span>
              <span>{globalPct}% — {formatBytes(finalTotals.sentBytes)} / {formatBytes(finalTotals.totalBytes)}</span>
            </div>
            <div className="h-2 bg-border rounded-full overflow-hidden">
              <div
                className="h-full bg-emerald-500 transition-all duration-300 rounded-full"
                style={{ width: `${globalPct ?? 0}%` }}
              />
            </div>
          </div>
        )}
      </div>
    </Card>
  );
}
