"use client";

import { useEffect, useState } from "react";
import {
  Trash2,
  ChevronRight,
  Cpu,
  Globe,
  Lock,
} from "lucide-react";
import { toast } from "sonner";
import { useQuery } from "@tanstack/react-query";
import type { AgentRuntime, Agent, MemberWithUser } from "@multica/core/types";
import { useAuthStore } from "@multica/core/auth";
import { useWorkspaceId } from "@multica/core/hooks";
import { memberListOptions, agentListOptions } from "@multica/core/workspace/queries";
import { useUpdateRuntime } from "@multica/core/runtimes/mutations";
import { deriveRuntimeHealth } from "@multica/core/runtimes";
import {
  type AgentPresenceDetail,
  useWorkspacePresenceMap,
} from "@multica/core/agents";
import { useWorkspacePaths } from "@multica/core/paths";
import { Button } from "@multica/ui/components/ui/button";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@multica/ui/components/ui/tooltip";
import { ActorAvatar } from "../../common/actor-avatar";
import { BreadcrumbHeader } from "../../layout/breadcrumb-header";
import { AppLink, useNavigation } from "../../navigation";
import { availabilityConfig, workloadConfig } from "../../agents/presence";
import { formatLastSeen, isSelfHealingRuntime } from "../utils";
import { HealthBadge } from "./shared";
import { ProviderLogo } from "./provider-logo";
import { UpdateSection } from "./update-section";
import { UsageSection } from "./usage-section";
import { DeleteRuntimeDialog } from "./delete-runtime-dialog";
import { useT } from "../../i18n";

function getCliVersion(metadata: Record<string, unknown>): string | null {
  if (
    metadata &&
    typeof metadata.cli_version === "string" &&
    metadata.cli_version
  ) {
    return metadata.cli_version;
  }
  return null;
}

function getLaunchedBy(metadata: Record<string, unknown>): string | null {
  if (
    metadata &&
    typeof metadata.launched_by === "string" &&
    metadata.launched_by
  ) {
    return metadata.launched_by;
  }
  return null;
}

function shortDaemonId(id: string | null): string | null {
  if (!id) return null;
  if (id.length <= 10) return id;
  return `${id.slice(0, 6)}··${id.slice(-2)}`;
}

// 30s tick keeps derived runtime health honest as time-based windows
// (recently_lost → offline → about_to_gc) cross thresholds without any new
// query data arriving. Agent presence has no time windows anymore, so it
// doesn't need this — but useWorkspacePresenceMap is the dependency we
// already mounted on this page, and that's wired to query data, not `now`.
function useNowTick(intervalMs = 30_000): number {
  const [now, setNow] = useState(() => Date.now());
  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), intervalMs);
    return () => clearInterval(id);
  }, [intervalMs]);
  return now;
}

export function RuntimeDetail({ runtime }: { runtime: AgentRuntime }) {
  const { t } = useT("runtimes");
  const cliVersion =
    runtime.runtime_mode === "local" ? getCliVersion(runtime.metadata) : null;
  const launchedBy =
    runtime.runtime_mode === "local" ? getLaunchedBy(runtime.metadata) : null;

  const user = useAuthStore((s) => s.user);
  const wsId = useWorkspaceId();
  const paths = useWorkspacePaths();
  const navigation = useNavigation();
  const { data: members = [] } = useQuery(memberListOptions(wsId));
  const { data: agents = [] } = useQuery(agentListOptions(wsId));
  const { byAgent: presenceMap } = useWorkspacePresenceMap(wsId);
  const now = useNowTick();

  const [deleteOpen, setDeleteOpen] = useState(false);

  const health = deriveRuntimeHealth(runtime, now);
  const ownerMember = runtime.owner_id
    ? members.find((m) => m.user_id === runtime.owner_id) ?? null
    : null;

  const currentMember = user
    ? members.find((m) => m.user_id === user.id)
    : null;
  const isAdmin = currentMember
    ? currentMember.role === "owner" || currentMember.role === "admin"
    : false;
  const isRuntimeOwner = user && runtime.owner_id === user.id;
  const canDelete = isAdmin || isRuntimeOwner;

  const servingAgents = agents.filter(
    (a) => a.runtime_id === runtime.id && !a.archived_at,
  );

  // Successful delete (light or cascade) closes the dialog and navigates
  // back to the runtimes list. Toast lives here so the cascade-mode count
  // and the light-mode "Runtime deleted" share one entry point.
  const handleDeleted = () => {
    setDeleteOpen(false);
    navigation.replace(paths.runtimes());
    toast.success(t(($) => $.detail.toast_deleted));
  };

  const daemonShort = shortDaemonId(runtime.daemon_id);
  const lastSeen = formatLastSeen(runtime.last_seen_at);

  return (
    <div className="flex h-full flex-col">
      <BreadcrumbHeader
        segments={[{ href: paths.runtimes(), label: t(($) => $.page.title) }]}
        leaf={
          <span className="truncate font-mono text-xs text-foreground">
            {runtime.name}
          </span>
        }
        actions={
          !canDelete ? (
            <span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
              <Lock className="h-3 w-3" />
              {t(($) => $.detail.read_only)}
            </span>
          ) : null
        }
      />

      {/* Body — single scroll container that owns the Hero card AND the
          analytic blocks below. Putting Hero inside the scroll (instead of
          pinning it under the topbar) means the scroll bar starts at the
          page boundary rather than mid-content; the topbar stays sticky on
          its own because it's navigation, not data. */}
      <div className="flex-1 min-h-0 overflow-y-auto">
        <div className="grid grid-cols-1 gap-4 p-6 lg:grid-cols-[minmax(0,1fr)_320px]">
          <div className="min-w-0 space-y-5">
            <HeroCard
              runtime={runtime}
              health={health}
              lastSeen={lastSeen}
              ownerMember={ownerMember}
              cliVersion={cliVersion}
              daemonShort={daemonShort}
            />
            <UsageSection runtime={runtime} />
          </div>

          {/* Right rail: serving agents + diagnostics */}
          <div className="space-y-4">
            <ServingAgentsCard
              agents={servingAgents}
              presenceMap={presenceMap}
              agentHref={(id) => paths.agentDetail(id)}
            />
            <DiagnosticsCard
              runtime={runtime}
              cliVersion={cliVersion}
              launchedBy={launchedBy}
              canDelete={!!canDelete}
              onDelete={() => setDeleteOpen(true)}
            />
          </div>
        </div>
      </div>

      {/* Delete confirmation — unified light/cascade dialog. Shared across
          this page and the runtime list kebab so the two entry points stay
          in lockstep on copy and behaviour. */}
      <DeleteRuntimeDialog
        open={deleteOpen}
        onOpenChange={setDeleteOpen}
        runtime={runtime}
        wsId={wsId}
        onDeleted={handleDeleted}
      />
    </div>
  );
}

// `device_info` arrives as a single composite string the daemon assembles
// (e.g. "host.local · 2.1.121 (Claude Code)"). Splitting on the first
// " · " gives us a hostname half + a runtime-version half so each can be
// labelled separately in the Hero card. Older runtimes that report just a
// hostname still work — `runtime` is undefined in that case.
function parseDeviceInfo(raw: string): { hostname: string; runtime?: string } {
  const idx = raw.indexOf(" · ");
  if (idx < 0) return { hostname: raw };
  return {
    hostname: raw.slice(0, idx),
    runtime: raw.slice(idx + 3),
  };
}

function HeroCard({
  runtime,
  health,
  lastSeen,
  ownerMember,
  cliVersion,
  daemonShort,
}: {
  runtime: AgentRuntime;
  health: ReturnType<typeof deriveRuntimeHealth>;
  lastSeen: string;
  ownerMember: MemberWithUser | null;
  cliVersion: string | null;
  daemonShort: string | null;
}) {
  const { t } = useT("runtimes");
  const [showDetails, setShowDetails] = useState(false);
  const device = runtime.device_info ? parseDeviceInfo(runtime.device_info) : null;
  const hasTechDetails = !!cliVersion || !!daemonShort;

  return (
    <div className="rounded-lg border bg-card">
      {/* Identity row — provider logo, name, status badge, last seen. */}
      <div className="flex items-start gap-3 border-b p-4">
        <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md border bg-card">
          <ProviderLogo provider={runtime.provider} className="h-5 w-5" />
        </div>
        <div className="min-w-0 flex-1">
          <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
            <h2 className="truncate text-base font-semibold tracking-tight">
              {runtime.name}
            </h2>
            <HealthBadge health={health} />
            <span className="text-xs text-muted-foreground">
              {t(($) => $.detail.last_seen, { when: lastSeen })}
            </span>
          </div>
        </div>
      </div>

      {/* User-visible facts — Owner / Device / Runtime, each labelled.
          Replaces the older dense `·`-separated meta strip that mixed
          everything (including dev-only IDs) at the same visual weight. */}
      <dl className="grid grid-cols-1 divide-y sm:grid-cols-3 sm:divide-x sm:divide-y-0">
        <Fact label="Owner">
          {ownerMember ? (
            <span className="inline-flex min-w-0 items-center gap-1.5">
              <ActorAvatar
                actorType="member"
                actorId={ownerMember.user_id}
                size={18}
                enableHoverCard
              />
              <span className="cursor-pointer truncate text-sm">{ownerMember.name}</span>
            </span>
          ) : (
            <span className="text-sm text-muted-foreground">—</span>
          )}
        </Fact>
        <Fact label="Device">
          {device?.hostname ? (
            <Tooltip>
              <TooltipTrigger
                render={
                  <span className="block truncate font-mono text-xs">
                    {device.hostname}
                  </span>
                }
              />
              <TooltipContent>{device.hostname}</TooltipContent>
            </Tooltip>
          ) : (
            <span className="text-sm text-muted-foreground">—</span>
          )}
        </Fact>
        <Fact label="Runtime">
          <span className="block truncate text-sm">
            {device?.runtime ?? (
              <span className="capitalize">{runtime.provider}</span>
            )}
          </span>
        </Fact>
      </dl>

      {/* Diagnostic IDs — multica CLI git hash + truncated daemon UUID.
          Only useful when filing an issue or reading logs; folded by
          default so they don't compete with the user-visible facts above. */}
      {hasTechDetails && (
        <div className="border-t">
          <button
            type="button"
            onClick={() => setShowDetails((v) => !v)}
            className="flex w-full items-center gap-1 px-4 py-2 text-xs text-muted-foreground transition-colors hover:text-foreground"
          >
            <ChevronRight
              className={`h-3 w-3 transition-transform ${
                showDetails ? "rotate-90" : ""
              }`}
            />
            {t(($) => $.detail.technical_details)}
          </button>
          {showDetails && (
            <dl className="grid grid-cols-1 gap-y-2 border-t bg-muted/30 px-4 py-3 sm:grid-cols-2">
              {cliVersion && (
                <Fact label="Daemon CLI" mono compact>
                  {cliVersion}
                </Fact>
              )}
              {daemonShort && (
                <Fact label="Daemon ID" mono compact>
                  {daemonShort}
                </Fact>
              )}
            </dl>
          )}
        </div>
      )}
    </div>
  );
}

function Fact({
  label,
  children,
  mono,
  compact,
}: {
  label: string;
  children: React.ReactNode;
  mono?: boolean;
  compact?: boolean;
}) {
  return (
    <div className={`min-w-0 ${compact ? "" : "px-4 py-3"}`}>
      <dt className="text-[11px] uppercase tracking-wider text-muted-foreground">
        {label}
      </dt>
      <dd className={`mt-1 ${mono ? "font-mono text-xs" : ""}`}>{children}</dd>
    </div>
  );
}

function ServingAgentsCard({
  agents,
  presenceMap,
  agentHref,
}: {
  agents: Agent[];
  presenceMap: Map<string, AgentPresenceDetail>;
  agentHref: (agentId: string) => string;
}) {
  const { t } = useT("runtimes");
  const { t: tAgents } = useT("agents");
  return (
    <div className="rounded-lg border">
      <div className="flex items-center justify-between border-b px-4 py-2.5">
        <span className="text-xs font-semibold">{t(($) => $.detail.serving_title)}</span>
        <span className="text-xs text-muted-foreground">
          {t(($) => $.detail.serving_count, { count: agents.length })}
        </span>
      </div>
      {agents.length === 0 ? (
        <div className="flex flex-col items-center px-4 py-6 text-center">
          <Cpu className="h-5 w-5 text-muted-foreground/40" />
          <p className="mt-2 text-xs text-muted-foreground">
            {t(($) => $.detail.no_agents)}
          </p>
        </div>
      ) : (
        <div className="divide-y">
          {agents.map((agent) => {
            const detail = presenceMap.get(agent.id);
            const av = detail
              ? availabilityConfig[detail.availability]
              : availabilityConfig.offline;
            const avLabel = tAgents(($) => $.availability[detail?.availability ?? "offline"]);
            const wl = detail ? workloadConfig[detail.workload] : null;
            const running = detail?.runningCount ?? 0;
            const queued = detail?.queuedCount ?? 0;
            return (
              <AppLink
                key={agent.id}
                href={agentHref(agent.id)}
                className="group flex items-center gap-2 px-4 py-2 transition-colors hover:bg-accent/40 focus-visible:bg-accent/40 focus-visible:outline-none"
              >
                <ActorAvatar actorType="agent" actorId={agent.id} size={20} enableHoverCard showStatusDot />
                <div className="min-w-0 flex-1">
                  <div className="truncate text-xs font-medium">
                    {agent.name}
                  </div>
                  <div className="mt-0.5 flex flex-wrap items-center gap-x-1.5 gap-y-0.5 text-xs">
                    <span className="inline-flex items-center gap-1.5">
                      <span className={`h-1.5 w-1.5 rounded-full ${av.dotClass}`} />
                      <span className={av.textClass}>{avLabel}</span>
                    </span>
                    {wl && detail && detail.workload !== "idle" && (
                      <span className={`inline-flex items-center gap-1 ${wl.textClass}`}>
                        <span className="text-muted-foreground">·</span>
                        <wl.icon
                          className={`h-3 w-3 ${detail.workload === "working" ? "animate-spin" : ""}`}
                        />
                        {tAgents(($) => $.workload[detail.workload])}
                        {running > 0 && (
                          <span className="text-muted-foreground">{t(($) => $.detail.running_chip, { count: running })}</span>
                        )}
                        {queued > 0 && (
                          <span className="text-muted-foreground">{t(($) => $.detail.queued_chip, { count: queued })}</span>
                        )}
                      </span>
                    )}
                  </div>
                </div>
                <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground/40 transition-colors group-hover:text-muted-foreground" />
              </AppLink>
            );
          })}
        </div>
      )}
    </div>
  );
}

function DiagnosticsCard({
  runtime,
  cliVersion,
  launchedBy,
  canDelete,
  onDelete,
}: {
  runtime: AgentRuntime;
  cliVersion: string | null;
  launchedBy: string | null;
  canDelete: boolean;
  onDelete: () => void;
}) {
  const { t } = useT("runtimes");
  const user = useAuthStore((s) => s.user);
  const isLocal = runtime.runtime_mode === "local";
  const selfHealing = isSelfHealingRuntime(runtime, user?.id);
  // canDelete here doubles as the "can edit runtime" predicate — it already
  // means "workspace owner/admin OR runtime owner", which is the same gate
  // the server enforces for the visibility PATCH.
  return (
    <div className="rounded-lg border">
      <div className="border-b px-4 py-2.5">
        <span className="text-xs font-semibold">{t(($) => $.detail.diagnostics_title)}</span>
      </div>
      <div className="space-y-3 p-4">
        <div>
          <div className="mb-1.5 text-[11px] uppercase tracking-wide text-muted-foreground">
            {t(($) => $.detail.diagnostics_visibility)}
          </div>
          {canDelete ? (
            <VisibilityEditor runtime={runtime} />
          ) : (
            <VisibilityReadout runtime={runtime} />
          )}
        </div>
        {isLocal && (
          <div className="border-t pt-3">
            <div className="mb-1.5 text-[11px] uppercase tracking-wide text-muted-foreground">
              {t(($) => $.detail.diagnostics_cli)}
            </div>
            <UpdateSection
              runtimeId={runtime.id}
              currentVersion={cliVersion}
              isOnline={runtime.status === "online"}
              launchedBy={launchedBy}
            />
          </div>
        )}
        {canDelete && (
          <div className="border-t pt-3">
            {selfHealing ? (
              <Tooltip>
                <TooltipTrigger
                  render={
                    // Wrapping span keeps the trigger hoverable — a disabled
                    // <button> swallows pointer events, so the tooltip would
                    // never open if it were the trigger itself.
                    <span className="block w-full">
                      <Button
                        variant="ghost"
                        size="sm"
                        disabled
                        className="h-8 w-full justify-start gap-2 text-destructive"
                      >
                        <Trash2 className="h-3.5 w-3.5" />
                        {t(($) => $.detail.delete_button)}
                      </Button>
                    </span>
                  }
                />
                <TooltipContent>
                  {t(($) => $.detail.delete_disabled_tooltip)}
                </TooltipContent>
              </Tooltip>
            ) : (
              <Button
                variant="ghost"
                size="sm"
                className="h-8 w-full justify-start gap-2 text-destructive hover:bg-destructive/10 hover:text-destructive"
                onClick={onDelete}
              >
                <Trash2 className="h-3.5 w-3.5" />
                {t(($) => $.detail.delete_button)}
              </Button>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

// VisibilityReadout renders a static "Private" / "Public" pill for users
// who can't edit the runtime. The description used to sit under the chip;
// it now lives in the hover tooltip so the Diagnostics column stays compact
// and matches the surrounding sections. Older backends that omit the field
// render as "Private" to match the strict default.
function VisibilityReadout({ runtime }: { runtime: AgentRuntime }) {
  const { t } = useT("runtimes");
  const visibility = runtime.visibility === "public" ? "public" : "private";
  const Icon = visibility === "public" ? Globe : Lock;
  return (
    <Tooltip>
      <TooltipTrigger
        render={
          <span className="inline-flex items-center gap-1.5 rounded-md border bg-muted/30 px-2 py-1.5 text-xs">
            <Icon className="h-3 w-3 text-muted-foreground" />
            <span className="font-medium">
              {t(($) => $.detail.visibility_label[visibility])}
            </span>
          </span>
        }
      />
      <TooltipContent>
        {t(($) => $.detail.visibility_hint[visibility])}
      </TooltipContent>
    </Tooltip>
  );
}

// VisibilityEditor lets the runtime owner (or workspace admin) flip
// public↔private. The PATCH endpoint also re-checks; this is a UI gate, not
// a security boundary. Per-choice description text lives in the hover
// tooltip so the two buttons stay a tight icon+label pair instead of the
// previous two-line block that competed with the surrounding cards.
function VisibilityEditor({ runtime }: { runtime: AgentRuntime }) {
  const { t } = useT("runtimes");
  const wsId = useWorkspaceId();
  const updateRuntime = useUpdateRuntime(wsId);
  const current = runtime.visibility === "public" ? "public" : "private";

  const flip = (next: "private" | "public") => {
    if (next === current) return;
    updateRuntime.mutate(
      { runtimeId: runtime.id, patch: { visibility: next } },
      {
        onSuccess: () =>
          toast.success(
            t(($) => $.detail.visibility_toast_updated, {
              visibility: t(($) => $.detail.visibility_label[next]),
            }),
          ),
        onError: (err) =>
          toast.error(
            err instanceof Error && err.message
              ? err.message
              : t(($) => $.detail.visibility_toast_failed),
          ),
      },
    );
  };

  return (
    <div className="inline-flex items-center gap-0.5 rounded-md bg-muted p-0.5">
      <VisibilityChoice
        active={current === "private"}
        icon={<Lock className="h-3 w-3" />}
        label={t(($) => $.detail.visibility_label.private)}
        tooltip={t(($) => $.detail.visibility_hint.private)}
        disabled={updateRuntime.isPending}
        onClick={() => flip("private")}
      />
      <VisibilityChoice
        active={current === "public"}
        icon={<Globe className="h-3 w-3" />}
        label={t(($) => $.detail.visibility_label.public)}
        tooltip={t(($) => $.detail.visibility_hint.public)}
        disabled={updateRuntime.isPending}
        onClick={() => flip("public")}
      />
    </div>
  );
}

function VisibilityChoice({
  active,
  icon,
  label,
  tooltip,
  disabled,
  onClick,
}: {
  active: boolean;
  icon: React.ReactNode;
  label: string;
  tooltip: string;
  disabled: boolean;
  onClick: () => void;
}) {
  return (
    <Tooltip>
      <TooltipTrigger
        render={
          <button
            type="button"
            onClick={onClick}
            disabled={disabled}
            className={`inline-flex items-center gap-1.5 rounded px-2 py-1 text-xs font-medium transition-colors ${
              active
                ? "bg-background text-foreground shadow-sm"
                : "text-muted-foreground hover:text-foreground"
            } ${disabled ? "cursor-not-allowed opacity-60" : ""}`}
          >
            <span className="shrink-0">{icon}</span>
            <span>{label}</span>
          </button>
        }
      />
      <TooltipContent>{tooltip}</TooltipContent>
    </Tooltip>
  );
}
