import { useEffect, useRef, useState } from 'react';
import {
  Activity,
  AlertTriangle,
  Loader2,
  Play,
  PlugZap,
  RotateCw,
  ScrollText,
  Square,
  Trash2,
  Unplug,
} from 'lucide-react';
import type { CompanionAction, CompanionContainer, CompanionHealthBody, CompanionVerdict } from './shared';
import { timeAgo } from './format';
import { useHeadscaleServers } from './useHeadscaleServers';
import {
  useCompanionAction,
  useCompanionHealth,
  useCompanionLogStream,
  useCompanionLogs,
} from './useHeadscaleCompanion';
import { Card, SectionHeader, Button, Dot, ErrorNote } from './Cards';
import { ViewShell } from './ViewShell';

// What the admin API structurally cannot tell you: is the container running, what did it log on the way
// down, and can we bring it back. All of it comes from the Officer Companion deployed alongside the server.
//
// The companion is optional and per-server, so "not deployed" is the ordinary case for a server that has
// never had one, and is rendered as an explanation rather than an error. Note the two inversions this
// section is built around: the companion's /health is ALWAYS HTTP 200 (read `verdict`, never the status),
// and an unavailable companion says nothing about Headscale itself — the admin API on the same domain is
// independent and the other sections may be working perfectly.

const VERDICTS: Record<CompanionVerdict, { tone: 'ok' | 'warn' | 'bad' | 'idle'; label: string; blurb: string }> = {
  ok: { tone: 'ok', label: 'Healthy', blurb: 'The container is running and Headscale is answering.' },
  degraded: {
    tone: 'warn',
    label: 'Degraded',
    blurb: 'The container is running, but Headscale is not answering properly.',
  },
  down: { tone: 'bad', label: 'Down', blurb: 'The container is not running.' },
  unknown: { tone: 'idle', label: 'Unknown', blurb: 'Docker does not know this container.' },
};

const ACTIONS: { id: CompanionAction; label: string; icon: typeof RotateCw; confirm: string }[] = [
  {
    id: 'restart',
    label: 'Restart',
    icon: RotateCw,
    confirm: 'Restart the Headscale container? Every node loses its control-plane connection until it is back.',
  },
  {
    id: 'stop',
    label: 'Stop',
    icon: Square,
    confirm: 'Stop the Headscale container? Every node stays disconnected until you start it again.',
  },
  { id: 'start', label: 'Start', icon: Play, confirm: 'Start the Headscale container?' },
];

/** The RFC3339 zero date the companion passes through from docker for a container that never finished. */
const isZeroDate = (iso: string) => iso.startsWith('0001-');

const Row = ({ label, value }: { label: string; value: React.ReactNode }) => (
  <div className="flex items-baseline justify-between gap-4 py-1.5 text-xs">
    <span className="shrink-0 text-zinc-500">{label}</span>
    <span className="min-w-0 truncate text-right text-zinc-300">{value}</span>
  </div>
);

const ContainerFacts = ({ container }: { container: CompanionContainer }) => (
  <div className="divide-y divide-white/5">
    <Row label="Container" value={container.status} />
    <Row label="Healthcheck" value={container.healthcheck ?? 'none defined'} />
    <Row label="Started" value={timeAgo(container.startedAt)} />
    {!container.running && !isZeroDate(container.finishedAt) && (
      <Row label="Exited" value={`${timeAgo(container.finishedAt)} · code ${container.exitCode}`} />
    )}
    <Row label="Restarts" value={container.restartCount === 0 ? 'none' : `${container.restartCount} by docker`} />
  </div>
);

/** Evidence, shown only when the verdict is not ok — likely causes first, then the raw tail behind them. */
const Evidence = ({ health }: { health: CompanionHealthBody }) => {
  const causes = health.likelyCauses ?? [];
  const recent = health.recentLogs ?? [];
  if (causes.length === 0 && recent.length === 0 && !health.healthcheckOutput) return null;

  return (
    <div className="flex flex-col gap-3 border-t border-white/10 p-4">
      {causes.length > 0 && (
        <div>
          <div className="mb-1.5 flex items-center gap-1.5 text-xs font-medium text-amber-300">
            <AlertTriangle className="h-3.5 w-3.5" />
            Likely causes
          </div>
          <ul className="flex flex-col gap-1">
            {causes.map((cause) => (
              <li key={cause} className="rounded-md bg-amber-500/10 px-2.5 py-1.5 text-xs text-amber-200/90">
                {cause}
              </li>
            ))}
          </ul>
          {/* The companion reads these out of the logs heuristically. Saying so is the difference between a
              hint the owner checks and a diagnosis they trust and then chase down the wrong hole. */}
          <p className="mt-1 text-[11px] text-zinc-600">Guessed from the logs — treat them as leads, not answers.</p>
        </div>
      )}

      {health.healthcheckOutput && (
        <div>
          <div className="mb-1.5 text-xs font-medium text-zinc-400">Healthcheck output</div>
          <pre className="overflow-x-auto rounded-md bg-black/50 p-2.5 font-mono text-[11px] whitespace-pre-wrap text-zinc-400">
            {health.healthcheckOutput}
          </pre>
        </div>
      )}

      {recent.length > 0 && (
        <div>
          <div className="mb-1.5 text-xs font-medium text-zinc-400">Last lines before now</div>
          <pre className="max-h-48 overflow-auto rounded-md bg-black/50 p-2.5 font-mono text-[11px] text-zinc-400">
            {recent.join('\n')}
          </pre>
        </div>
      )}
    </div>
  );
};

const TAILS = [100, 500, 2000];

const LogViewer = () => {
  const [tail, setTail] = useState(200);
  const [follow, setFollow] = useState(false);
  const snapshot = useCompanionLogs(tail, !follow);
  const stream = useCompanionLogStream(follow, tail);
  const boxRef = useRef<HTMLPreElement>(null);

  const lines = follow ? stream.lines : snapshot.data?.available ? snapshot.data.lines : [];

  // Pin to the bottom while following. Only while following: scrolling a snapshot back to the top and having
  // it yanked down again would be the viewer fighting the reader.
  useEffect(() => {
    if (follow && boxRef.current) boxRef.current.scrollTop = boxRef.current.scrollHeight;
  }, [follow, lines.length]);

  const unavailable = !follow && snapshot.data && !snapshot.data.available ? snapshot.data.reason : null;

  return (
    <Card>
      <div className="flex flex-wrap items-center gap-2 border-b border-white/10 px-3 py-2">
        <div className="mr-auto flex items-center gap-1.5 text-xs font-medium text-zinc-300">
          <ScrollText className="h-3.5 w-3.5" />
          Logs
          {follow && (
            <span className="ml-1 inline-flex items-center gap-1 text-[11px] text-zinc-500">
              <Dot tone={stream.live ? 'ok' : 'idle'} />
              {stream.live ? 'live' : 'stopped'}
            </span>
          )}
        </div>

        <div className="flex items-center gap-1">
          {TAILS.map((n) => (
            <Button key={n} variant={tail === n ? 'primary' : 'ghost'} onClick={() => setTail(n)}>
              {n}
            </Button>
          ))}
        </div>
        <Button variant={follow ? 'primary' : 'ghost'} onClick={() => setFollow((on) => !on)}>
          {follow ? <Unplug className="h-3.5 w-3.5" /> : <PlugZap className="h-3.5 w-3.5" />}
          {follow ? 'Stop' : 'Follow'}
        </Button>
        {follow ? (
          <Button onClick={stream.clear} title="Clear what has been received">
            <Trash2 className="h-3.5 w-3.5" />
          </Button>
        ) : (
          <Button onClick={() => void snapshot.refetch()} disabled={snapshot.isFetching}>
            {snapshot.isFetching ? (
              <Loader2 className="h-3.5 w-3.5 animate-spin" />
            ) : (
              <RotateCw className="h-3.5 w-3.5" />
            )}
            Refresh
          </Button>
        )}
      </div>

      {stream.error && (
        <div className="border-b border-white/10 px-3 py-2 text-[11px] text-red-300">{stream.error}</div>
      )}
      {unavailable && <div className="border-b border-white/10 px-3 py-2 text-[11px] text-zinc-500">{unavailable}</div>}

      <pre
        ref={boxRef}
        className="max-h-[26rem] min-h-[12rem] overflow-auto bg-black/40 p-3 font-mono text-[11px] leading-relaxed text-zinc-400"
      >
        {lines.length > 0
          ? lines.join('\n')
          : snapshot.isLoading
            ? 'Loading…'
            : follow
              ? 'Waiting for output…'
              : 'No log lines.'}
      </pre>
    </Card>
  );
};

const Lifecycle = ({ running }: { running: boolean | null }) => {
  const action = useCompanionAction();
  const [pending, setPending] = useState<CompanionAction | null>(null);

  const run = async (id: CompanionAction, confirm: string) => {
    if (!window.confirm(confirm)) return;
    setPending(id);
    try {
      await action.mutateAsync(id);
    } catch {
      /* surfaced from action.error below */
    } finally {
      setPending(null);
    }
  };

  const result = action.data;

  return (
    <div className="flex flex-col gap-2 border-t border-white/10 p-4">
      <div className="flex flex-wrap items-center gap-2">
        {ACTIONS.map(({ id, label, icon: Icon, confirm }) => (
          <Button
            key={id}
            variant={id === 'stop' ? 'danger' : 'ghost'}
            disabled={pending !== null || (running !== null && (id === 'start' ? running : !running))}
            onClick={() => void run(id, confirm)}
            title={id === 'start' && running ? 'Already running' : undefined}
          >
            {pending === id ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Icon className="h-3.5 w-3.5" />}
            {label}
          </Button>
        ))}
        <span className="text-[11px] text-zinc-600">Acts on the container, not on Officer.</span>
      </div>

      {action.error != null && <ErrorNote>The action could not be sent.</ErrorNote>}
      {result && !result.available && <ErrorNote>{result.reason}</ErrorNote>}
      {result && result.available && !result.ok && (
        <ErrorNote>{result.error ?? 'Docker refused the action.'}</ErrorNote>
      )}
      {result && result.available && result.ok && (
        <div className="text-[11px] text-emerald-300">
          {result.action}: {result.result}
        </div>
      )}
    </div>
  );
};

export const DiagnosticsView = () => {
  const { active } = useHeadscaleServers();
  const query = useCompanionHealth();
  const result = query.data;

  return (
    <ViewShell isLoading={query.isLoading} error={query.error} label="diagnostics">
      <div className="mx-auto flex max-w-3xl flex-col gap-4">
        <SectionHeader
          title="Diagnostics"
          subtitle={active ? `The container behind ${active.name}, as seen from the machine it runs on.` : undefined}
          action={query.isFetching ? <Loader2 className="h-3.5 w-3.5 animate-spin text-zinc-600" /> : undefined}
        />

        {result && !result.available ? (
          <Card>
            <div className="flex flex-col gap-2 p-4">
              <div className="flex items-center gap-2 text-sm font-semibold text-zinc-100">
                <Activity className="h-4 w-4 text-zinc-500" />
                No companion on this server
              </div>
              <p className="text-xs leading-relaxed text-zinc-500">
                {result.reason}. The Officer Companion is a small service deployed next to Headscale that can see its
                container — it is what makes health, logs and restart possible from here.
              </p>
              <p className="text-xs leading-relaxed text-zinc-600">
                This says nothing about Headscale itself: it is served by the same domain but a different process, so
                the other sections may be working normally. When the companion is missing, the Console section is the
                way in.
              </p>
            </div>
          </Card>
        ) : result ? (
          <>
            <Card>
              <div className="flex items-start gap-3 p-4">
                <div className="mt-1">
                  <Dot tone={VERDICTS[result.health.verdict].tone} />
                </div>
                <div className="min-w-0 flex-1">
                  <div className="text-sm font-semibold text-zinc-100">{VERDICTS[result.health.verdict].label}</div>
                  <p className="mt-0.5 text-xs text-zinc-500">
                    {result.health.reason ?? VERDICTS[result.health.verdict].blurb}
                  </p>
                </div>
              </div>

              <div className="border-t border-white/10 px-4 py-2">
                <Row label="Control plane" value={result.health.connected ? 'answering' : 'not answering'} />
                {result.health.probe && <Row label="Probe" value={result.health.probe} />}
                {result.health.container && <ContainerFacts container={result.health.container} />}
              </div>

              <Evidence health={result.health} />
              <Lifecycle running={result.health.container?.running ?? null} />
            </Card>

            <LogViewer />
          </>
        ) : null}
      </div>
    </ViewShell>
  );
};
