import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
import { withProgress } from "../cli/progress.js";
import { formatCliCommand } from "../cli/command-format.js";
import { loadConfig, readConfigFileSnapshot, resolveGatewayPort } from "../config/config.js";
import { readLastGatewayErrorLine } from "../daemon/diagnostics.js";
import type { GatewayService } from "../daemon/service.js";
import { resolveGatewayService } from "../daemon/service.js";
import { resolveNodeService } from "../daemon/node-service.js";
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
import { normalizeControlUiBasePath } from "../gateway/control-ui-shared.js";
import { probeGateway } from "../gateway/probe.js";
import { collectChannelStatusIssues } from "../infra/channels-status-issues.js";
import { resolveClawdbotPackageRoot } from "../infra/clawdbot-root.js";
import { resolveOsSummary } from "../infra/os-summary.js";
import { inspectPortUsage } from "../infra/ports.js";
import { readRestartSentinel } from "../infra/restart-sentinel.js";
import { readTailscaleStatusJson } from "../infra/tailscale.js";
import { checkUpdateStatus, compareSemverStrings } from "../infra/update-check.js";
import {
  formatUpdateChannelLabel,
  normalizeUpdateChannel,
  resolveEffectiveUpdateChannel,
} from "../infra/update-channels.js";
import { getRemoteSkillEligibility } from "../infra/skills-remote.js";
import { runExec } from "../process/exec.js";
import type { RuntimeEnv } from "../runtime.js";
import { VERSION } from "../version.js";
import { resolveControlUiLinks } from "./onboard-helpers.js";
import { getAgentLocalStatuses } from "./status-all/agents.js";
import { buildChannelsTable } from "./status-all/channels.js";
import { formatDuration, formatGatewayAuthUsed } from "./status-all/format.js";
import { pickGatewaySelfPresence } from "./status-all/gateway.js";
import { buildStatusAllReportLines } from "./status-all/report-lines.js";

export async function statusAllCommand(
  runtime: RuntimeEnv,
  opts?: { timeoutMs?: number },
): Promise<void> {
  await withProgress({ label: "Scanning status --all…", total: 11 }, async (progress) => {
    progress.setLabel("Loading config…");
    const cfg = loadConfig();
    const osSummary = resolveOsSummary();
    const snap = await readConfigFileSnapshot().catch(() => null);
    progress.tick();

    progress.setLabel("Checking Tailscale…");
    const tailscaleMode = cfg.gateway?.tailscale?.mode ?? "off";
    const tailscale = await (async () => {
      try {
        const parsed = await readTailscaleStatusJson(runExec, {
          timeoutMs: 1200,
        });
        const backendState = typeof parsed.BackendState === "string" ? parsed.BackendState : null;
        const self =
          typeof parsed.Self === "object" && parsed.Self !== null
            ? (parsed.Self as Record<string, unknown>)
            : null;
        const dnsNameRaw = self && typeof self.DNSName === "string" ? self.DNSName : null;
        const dnsName = dnsNameRaw ? dnsNameRaw.replace(/\.$/, "") : null;
        const ips =
          self && Array.isArray(self.TailscaleIPs)
            ? (self.TailscaleIPs as unknown[])
                .filter((v) => typeof v === "string" && v.trim().length > 0)
                .map((v) => (v as string).trim())
            : [];
        return { ok: true as const, backendState, dnsName, ips, error: null };
      } catch (err) {
        return {
          ok: false as const,
          backendState: null,
          dnsName: null,
          ips: [] as string[],
          error: String(err),
        };
      }
    })();
    const tailscaleHttpsUrl =
      tailscaleMode !== "off" && tailscale.dnsName
        ? `https://${tailscale.dnsName}${normalizeControlUiBasePath(cfg.gateway?.controlUi?.basePath)}`
        : null;
    progress.tick();

    progress.setLabel("Checking for updates…");
    const root = await resolveClawdbotPackageRoot({
      moduleUrl: import.meta.url,
      argv1: process.argv[1],
      cwd: process.cwd(),
    });
    const update = await checkUpdateStatus({
      root,
      timeoutMs: 6500,
      fetchGit: true,
      includeRegistry: true,
    });
    const configChannel = normalizeUpdateChannel(cfg.update?.channel);
    const channelInfo = resolveEffectiveUpdateChannel({
      configChannel,
      installKind: update.installKind,
      git: update.git ? { tag: update.git.tag, branch: update.git.branch } : undefined,
    });
    const channelLabel = formatUpdateChannelLabel({
      channel: channelInfo.channel,
      source: channelInfo.source,
      gitTag: update.git?.tag ?? null,
      gitBranch: update.git?.branch ?? null,
    });
    const gitLabel =
      update.installKind === "git"
        ? (() => {
            const shortSha = update.git?.sha ? update.git.sha.slice(0, 8) : null;
            const branch =
              update.git?.branch && update.git.branch !== "HEAD" ? update.git.branch : null;
            const tag = update.git?.tag ?? null;
            const parts = [
              branch ?? (tag ? "detached" : "git"),
              tag ? `tag ${tag}` : null,
              shortSha ? `@ ${shortSha}` : null,
            ].filter(Boolean);
            return parts.join(" · ");
          })()
        : null;
    progress.tick();

    progress.setLabel("Probing gateway…");
    const connection = buildGatewayConnectionDetails({ config: cfg });
    const isRemoteMode = cfg.gateway?.mode === "remote";
    const remoteUrlRaw =
      typeof cfg.gateway?.remote?.url === "string" ? cfg.gateway.remote.url.trim() : "";
    const remoteUrlMissing = isRemoteMode && !remoteUrlRaw;
    const gatewayMode = isRemoteMode ? "remote" : "local";

    const resolveProbeAuth = (mode: "local" | "remote") => {
      const authToken = cfg.gateway?.auth?.token;
      const authPassword = cfg.gateway?.auth?.password;
      const remote = cfg.gateway?.remote;
      const token =
        mode === "remote"
          ? typeof remote?.token === "string" && remote.token.trim()
            ? remote.token.trim()
            : undefined
          : process.env.OPENCLAW_GATEWAY_TOKEN?.trim() ||
            (typeof authToken === "string" && authToken.trim() ? authToken.trim() : undefined);
      const password =
        process.env.OPENCLAW_GATEWAY_PASSWORD?.trim() ||
        (mode === "remote"
          ? typeof remote?.password === "string" && remote.password.trim()
            ? remote.password.trim()
            : undefined
          : typeof authPassword === "string" && authPassword.trim()
            ? authPassword.trim()
            : undefined);
      return { token, password };
    };

    const localFallbackAuth = resolveProbeAuth("local");
    const remoteAuth = resolveProbeAuth("remote");
    const probeAuth = isRemoteMode && !remoteUrlMissing ? remoteAuth : localFallbackAuth;

    const gatewayProbe = await probeGateway({
      url: connection.url,
      auth: probeAuth,
      timeoutMs: Math.min(5000, opts?.timeoutMs ?? 10_000),
    }).catch(() => null);
    const gatewayReachable = gatewayProbe?.ok === true;
    const gatewaySelf = pickGatewaySelfPresence(gatewayProbe?.presence ?? null);
    progress.tick();

    progress.setLabel("Checking services…");
    const readServiceSummary = async (service: GatewayService) => {
      try {
        const [loaded, runtimeInfo, command] = await Promise.all([
          service.isLoaded({ env: process.env }).catch(() => false),
          service.readRuntime(process.env).catch(() => undefined),
          service.readCommand(process.env).catch(() => null),
        ]);
        const installed = command != null;
        return {
          label: service.label,
          installed,
          loaded,
          loadedText: loaded ? service.loadedText : service.notLoadedText,
          runtime: runtimeInfo,
        };
      } catch {
        return null;
      }
    };
    const daemon = await readServiceSummary(resolveGatewayService());
    const nodeService = await readServiceSummary(resolveNodeService());
    progress.tick();

    progress.setLabel("Scanning agents…");
    const agentStatus = await getAgentLocalStatuses(cfg);
    progress.tick();
    progress.setLabel("Summarizing channels…");
    const channels = await buildChannelsTable(cfg, { showSecrets: false });
    progress.tick();

    const connectionDetailsForReport = (() => {
      if (!remoteUrlMissing) return connection.message;
      const bindMode = cfg.gateway?.bind ?? "loopback";
      const configPath = snap?.path?.trim() ? snap.path.trim() : "(unknown config path)";
      return [
        "Gateway mode: remote",
        "Gateway target: (missing gateway.remote.url)",
        `Config: ${configPath}`,
        `Bind: ${bindMode}`,
        `Local fallback (used for probes): ${connection.url}`,
        "Fix: set gateway.remote.url, or set gateway.mode=local.",
      ].join("\n");
    })();

    const callOverrides = remoteUrlMissing
      ? {
          url: connection.url,
          token: localFallbackAuth.token,
          password: localFallbackAuth.password,
        }
      : {};

    progress.setLabel("Querying gateway…");
    const health = gatewayReachable
      ? await callGateway<unknown>({
          method: "health",
          timeoutMs: Math.min(8000, opts?.timeoutMs ?? 10_000),
          ...callOverrides,
        }).catch((err) => ({ error: String(err) }))
      : { error: gatewayProbe?.error ?? "gateway unreachable" };

    const channelsStatus = gatewayReachable
      ? await callGateway<Record<string, unknown>>({
          method: "channels.status",
          params: { probe: false, timeoutMs: opts?.timeoutMs ?? 10_000 },
          timeoutMs: Math.min(8000, opts?.timeoutMs ?? 10_000),
          ...callOverrides,
        }).catch(() => null)
      : null;
    const channelIssues = channelsStatus ? collectChannelStatusIssues(channelsStatus) : [];
    progress.tick();

    progress.setLabel("Checking local state…");
    const sentinel = await readRestartSentinel().catch(() => null);
    const lastErr = await readLastGatewayErrorLine(process.env).catch(() => null);
    const port = resolveGatewayPort(cfg);
    const portUsage = await inspectPortUsage(port).catch(() => null);
    progress.tick();

    const defaultWorkspace =
      agentStatus.agents.find((a) => a.id === agentStatus.defaultId)?.workspaceDir ??
      agentStatus.agents[0]?.workspaceDir ??
      null;
    const skillStatus =
      defaultWorkspace != null
        ? (() => {
            try {
              return buildWorkspaceSkillStatus(defaultWorkspace, {
                config: cfg,
                eligibility: { remote: getRemoteSkillEligibility() },
              });
            } catch {
              return null;
            }
          })()
        : null;

    const controlUiEnabled = cfg.gateway?.controlUi?.enabled ?? true;
    const dashboard = controlUiEnabled
      ? resolveControlUiLinks({
          port,
          bind: cfg.gateway?.bind,
          customBindHost: cfg.gateway?.customBindHost,
          basePath: cfg.gateway?.controlUi?.basePath,
        }).httpUrl
      : null;

    const updateLine = (() => {
      if (update.installKind === "git" && update.git) {
        const parts: string[] = [];
        parts.push(update.git.branch ? `git ${update.git.branch}` : "git");
        if (update.git.upstream) parts.push(`↔ ${update.git.upstream}`);
        if (update.git.dirty) parts.push("dirty");
        if (update.git.behind != null && update.git.ahead != null) {
          if (update.git.behind === 0 && update.git.ahead === 0) parts.push("up to date");
          else if (update.git.behind > 0 && update.git.ahead === 0)
            parts.push(`behind ${update.git.behind}`);
          else if (update.git.behind === 0 && update.git.ahead > 0)
            parts.push(`ahead ${update.git.ahead}`);
          else parts.push(`diverged (ahead ${update.git.ahead}, behind ${update.git.behind})`);
        }
        if (update.git.fetchOk === false) parts.push("fetch failed");

        const latest = update.registry?.latestVersion;
        if (latest) {
          const cmp = compareSemverStrings(VERSION, latest);
          if (cmp === 0) parts.push(`npm latest ${latest}`);
          else if (cmp != null && cmp < 0) parts.push(`npm update ${latest}`);
          else parts.push(`npm latest ${latest} (local newer)`);
        } else if (update.registry?.error) {
          parts.push("npm latest unknown");
        }

        if (update.deps?.status === "ok") parts.push("deps ok");
        if (update.deps?.status === "stale") parts.push("deps stale");
        if (update.deps?.status === "missing") parts.push("deps missing");
        return parts.join(" · ");
      }
      const parts: string[] = [];
      parts.push(update.packageManager !== "unknown" ? update.packageManager : "pkg");
      const latest = update.registry?.latestVersion;
      if (latest) {
        const cmp = compareSemverStrings(VERSION, latest);
        if (cmp === 0) parts.push(`npm latest ${latest}`);
        else if (cmp != null && cmp < 0) parts.push(`npm update ${latest}`);
        else parts.push(`npm latest ${latest} (local newer)`);
      } else if (update.registry?.error) {
        parts.push("npm latest unknown");
      }
      if (update.deps?.status === "ok") parts.push("deps ok");
      if (update.deps?.status === "stale") parts.push("deps stale");
      if (update.deps?.status === "missing") parts.push("deps missing");
      return parts.join(" · ");
    })();

    const gatewayTarget = remoteUrlMissing ? `fallback ${connection.url}` : connection.url;
    const gatewayStatus = gatewayReachable
      ? `reachable ${formatDuration(gatewayProbe?.connectLatencyMs)}`
      : gatewayProbe?.error
        ? `unreachable (${gatewayProbe.error})`
        : "unreachable";
    const gatewayAuth = gatewayReachable ? ` · auth ${formatGatewayAuthUsed(probeAuth)}` : "";
    const gatewaySelfLine =
      gatewaySelf?.host || gatewaySelf?.ip || gatewaySelf?.version || gatewaySelf?.platform
        ? [
            gatewaySelf.host ? gatewaySelf.host : null,
            gatewaySelf.ip ? `(${gatewaySelf.ip})` : null,
            gatewaySelf.version ? `app ${gatewaySelf.version}` : null,
            gatewaySelf.platform ? gatewaySelf.platform : null,
          ]
            .filter(Boolean)
            .join(" ")
        : null;

    const aliveThresholdMs = 10 * 60_000;
    const aliveAgents = agentStatus.agents.filter(
      (a) => a.lastActiveAgeMs != null && a.lastActiveAgeMs <= aliveThresholdMs,
    ).length;

    const overviewRows = [
      { Item: "Version", Value: VERSION },
      { Item: "OS", Value: osSummary.label },
      { Item: "Node", Value: process.versions.node },
      {
        Item: "Config",
        Value: snap?.path?.trim() ? snap.path.trim() : "(unknown config path)",
      },
      dashboard
        ? { Item: "Dashboard", Value: dashboard }
        : { Item: "Dashboard", Value: "disabled" },
      {
        Item: "Tailscale",
        Value:
          tailscaleMode === "off"
            ? `off${tailscale.backendState ? ` · ${tailscale.backendState}` : ""}${tailscale.dnsName ? ` · ${tailscale.dnsName}` : ""}`
            : tailscale.dnsName && tailscaleHttpsUrl
              ? `${tailscaleMode} · ${tailscale.backendState ?? "unknown"} · ${tailscale.dnsName} · ${tailscaleHttpsUrl}`
              : `${tailscaleMode} · ${tailscale.backendState ?? "unknown"} · magicdns unknown`,
      },
      { Item: "Channel", Value: channelLabel },
      ...(gitLabel ? [{ Item: "Git", Value: gitLabel }] : []),
      { Item: "Update", Value: updateLine },
      {
        Item: "Gateway",
        Value: `${gatewayMode}${remoteUrlMissing ? " (remote.url missing)" : ""} · ${gatewayTarget} (${connection.urlSource}) · ${gatewayStatus}${gatewayAuth}`,
      },
      { Item: "Security", Value: `Run: ${formatCliCommand("openclaw-cn security audit --deep")}` },
      gatewaySelfLine
        ? { Item: "Gateway self", Value: gatewaySelfLine }
        : { Item: "Gateway self", Value: "unknown" },
      daemon
        ? {
            Item: "Gateway service",
            Value:
              daemon.installed === false
                ? `${daemon.label} not installed`
                : `${daemon.label} ${daemon.installed ? "installed · " : ""}${daemon.loadedText}${daemon.runtime?.status ? ` · ${daemon.runtime.status}` : ""}${daemon.runtime?.pid ? ` (pid ${daemon.runtime.pid})` : ""}`,
          }
        : { Item: "Gateway service", Value: "unknown" },
      nodeService
        ? {
            Item: "Node service",
            Value:
              nodeService.installed === false
                ? `${nodeService.label} not installed`
                : `${nodeService.label} ${nodeService.installed ? "installed · " : ""}${nodeService.loadedText}${nodeService.runtime?.status ? ` · ${nodeService.runtime.status}` : ""}${nodeService.runtime?.pid ? ` (pid ${nodeService.runtime.pid})` : ""}`,
          }
        : { Item: "Node service", Value: "unknown" },
      {
        Item: "Agents",
        Value: `${agentStatus.agents.length} total · ${agentStatus.bootstrapPendingCount} bootstrapping · ${aliveAgents} active · ${agentStatus.totalSessions} sessions`,
      },
    ];

    const lines = await buildStatusAllReportLines({
      progress,
      overviewRows,
      channels,
      channelIssues: channelIssues.map((issue) => ({
        channel: issue.channel,
        message: issue.message,
      })),
      agentStatus,
      connectionDetailsForReport,
      diagnosis: {
        snap,
        remoteUrlMissing,
        sentinel,
        lastErr,
        port,
        portUsage,
        tailscaleMode,
        tailscale,
        tailscaleHttpsUrl,
        skillStatus,
        channelsStatus,
        channelIssues,
        gatewayReachable,
        health,
      },
    });

    progress.setLabel("Rendering…");
    runtime.log(lines.join("\n"));
    progress.tick();
  });
}
