// Status scan overview tests cover overview collection and gateway/runtime summary inputs.
import { beforeEach, describe, expect, it, vi } from "vitest";
import { collectStatusScanOverview } from "./status.scan-overview.ts";

const mocks = vi.hoisted(() => ({
  hasConfiguredChannelsForReadOnlyScope: vi.fn(),
  resolveCommandConfigWithSecrets: vi.fn(),
  getStatusCommandSecretTargetIds: vi.fn(),
  readBestEffortConfigSnapshot: vi.fn(),
  resolveGatewayPort: vi.fn(),
  resolveOsSummary: vi.fn(),
  createStatusScanCoreBootstrap: vi.fn(),
  callGateway: vi.fn(),
  collectChannelStatusIssues: vi.fn(),
  buildChannelsTable: vi.fn(),
}));

vi.mock("../plugins/channel-plugin-ids.js", () => ({
  hasConfiguredChannelsForReadOnlyScope: mocks.hasConfiguredChannelsForReadOnlyScope,
}));

vi.mock("../cli/command-config-resolution.js", () => ({
  resolveCommandConfigWithSecrets: mocks.resolveCommandConfigWithSecrets,
}));

vi.mock("../cli/command-secret-targets.js", () => ({
  getStatusCommandSecretTargetIds: mocks.getStatusCommandSecretTargetIds,
}));

vi.mock("../config/config.js", () => ({
  readBestEffortConfigSnapshot: mocks.readBestEffortConfigSnapshot,
  resolveGatewayPort: mocks.resolveGatewayPort,
}));

vi.mock("../infra/os-summary.js", () => ({
  resolveOsSummary: mocks.resolveOsSummary,
}));

vi.mock("./status.scan.bootstrap-shared.js", () => ({
  createStatusScanCoreBootstrap: mocks.createStatusScanCoreBootstrap,
}));

vi.mock("../gateway/call.js", () => ({
  callGateway: mocks.callGateway,
}));

vi.mock("./status.scan.runtime.js", () => ({
  statusScanRuntime: {
    collectChannelStatusIssues: mocks.collectChannelStatusIssues,
    buildChannelsTable: mocks.buildChannelsTable,
  },
}));

function firstGatewayRequest(): { method?: string; url?: string; token?: string } {
  const call = mocks.callGateway.mock.calls[0];
  if (!call) {
    throw new Error("expected gateway call");
  }
  return call[0] as { method?: string; url?: string; token?: string };
}

type ChannelsTableCall = [
  unknown,
  {
    includeSetupFallbackPlugins?: boolean;
    showSecrets?: boolean;
    sourceConfig?: unknown;
  },
];

function firstChannelsTableCall(): ChannelsTableCall {
  const call = mocks.buildChannelsTable.mock.calls[0];
  if (!call) {
    throw new Error("expected channels table call");
  }
  return call as ChannelsTableCall;
}

describe("collectStatusScanOverview", () => {
  beforeEach(() => {
    vi.clearAllMocks();

    mocks.hasConfiguredChannelsForReadOnlyScope.mockReturnValue(true);
    mocks.getStatusCommandSecretTargetIds.mockReturnValue([]);
    mocks.readBestEffortConfigSnapshot.mockResolvedValue({
      config: { session: {} },
      sourceConfig: { session: { raw: true } },
    });
    mocks.resolveCommandConfigWithSecrets.mockResolvedValue({
      resolvedConfig: { session: {} },
      diagnostics: ["secret warning"],
    });
    mocks.resolveOsSummary.mockReturnValue({ label: "test-os" });
    mocks.createStatusScanCoreBootstrap.mockResolvedValue({
      tailscaleMode: "serve",
      tailscaleDnsPromise: Promise.resolve("box.tail.ts.net"),
      updatePromise: Promise.resolve({ installKind: "git" }),
      agentStatusPromise: Promise.resolve({
        defaultId: "main",
        agents: [],
        totalSessions: 0,
        bootstrapPendingCount: 0,
      }),
      gatewayProbePromise: Promise.resolve({
        gatewayConnection: {
          url: "ws://127.0.0.1:18789",
          urlSource: "missing gateway.remote.url (fallback local)",
        },
        remoteUrlMissing: true,
        gatewayMode: "remote",
        gatewayProbeAuth: { token: "tok" },
        gatewayProbeAuthWarning: "warn",
        gatewayProbe: { ok: true, error: null },
        gatewayReachable: true,
        gatewaySelf: { host: "box" },
        gatewayCallOverrides: {
          url: "ws://127.0.0.1:18789",
          token: "tok",
        },
      }),
      resolveTailscaleHttpsUrl: vi.fn(async () => "https://box.tail.ts.net"),
      skipColdStartNetworkChecks: false,
    });
    mocks.callGateway.mockResolvedValue({ channelAccounts: {} });
    mocks.collectChannelStatusIssues.mockReturnValue([{ channel: "quietchat", message: "boom" }]);
    mocks.buildChannelsTable.mockResolvedValue({ rows: [], details: [] });
  });

  it("uses gateway fallback overrides for channels.status when requested", async () => {
    const result = await collectStatusScanOverview({
      commandName: "status --all",
      opts: { timeoutMs: 1234 },
      showSecrets: false,
      useGatewayCallOverridesForChannelsStatus: true,
    });

    expect(mocks.readBestEffortConfigSnapshot).toHaveBeenCalledWith({
      observe: false,
      skipPluginValidation: undefined,
    });
    expect(mocks.callGateway).toHaveBeenCalledOnce();
    const gatewayRequest = firstGatewayRequest();
    expect(gatewayRequest?.method).toBe("channels.status");
    expect(gatewayRequest?.url).toBe("ws://127.0.0.1:18789");
    expect(gatewayRequest?.token).toBe("tok");
    expect(mocks.buildChannelsTable).toHaveBeenCalledOnce();
    const channelTableCall = firstChannelsTableCall();
    expect(typeof channelTableCall?.[0]).toBe("object");
    expect(channelTableCall?.[1]?.includeSetupFallbackPlugins).toBe(true);
    expect(channelTableCall?.[1]?.showSecrets).toBe(false);
    expect(channelTableCall?.[1]?.sourceConfig).toStrictEqual({ session: { raw: true } });
    expect(result.channelIssues).toEqual([{ channel: "quietchat", message: "boom" }]);
  });

  it("can keep channel overview on metadata-only status paths", async () => {
    const result = await collectStatusScanOverview({
      commandName: "status",
      opts: { timeoutMs: 1234 },
      showSecrets: false,
      includeLiveChannelStatus: false,
      includeChannelSetupRuntimeFallback: false,
    });

    expect(mocks.callGateway).not.toHaveBeenCalled();
    expect(mocks.buildChannelsTable).toHaveBeenCalledOnce();
    const channelTableCall = firstChannelsTableCall();
    expect(typeof channelTableCall?.[0]).toBe("object");
    expect(channelTableCall?.[1]?.includeSetupFallbackPlugins).toBe(false);
    expect(channelTableCall?.[1]?.showSecrets).toBe(false);
    expect(channelTableCall?.[1]?.sourceConfig).toStrictEqual({ session: { raw: true } });
    expect(result.channelIssues).toStrictEqual([]);
  });

  it("skips channels.status when the gateway is unreachable", async () => {
    mocks.createStatusScanCoreBootstrap.mockResolvedValueOnce({
      tailscaleMode: "off",
      tailscaleDnsPromise: Promise.resolve(null),
      updatePromise: Promise.resolve({ installKind: "git" }),
      agentStatusPromise: Promise.resolve({
        defaultId: "main",
        agents: [],
        totalSessions: 0,
        bootstrapPendingCount: 0,
      }),
      gatewayProbePromise: Promise.resolve({
        gatewayConnection: {
          url: "ws://127.0.0.1:18789",
          urlSource: "default",
        },
        remoteUrlMissing: false,
        gatewayMode: "local",
        gatewayProbeAuth: {},
        gatewayProbeAuthWarning: undefined,
        gatewayProbe: null,
        gatewayReachable: false,
        gatewaySelf: null,
      }),
      resolveTailscaleHttpsUrl: vi.fn(async () => null),
      skipColdStartNetworkChecks: false,
    });
    const result = await collectStatusScanOverview({
      commandName: "status",
      opts: {},
      showSecrets: true,
    });

    expect(mocks.callGateway).not.toHaveBeenCalled();
    expect(result.channelsStatus).toBeNull();
    expect(result.channelIssues).toStrictEqual([]);
  });
});
