import type { Client } from "ssh2";
import { describe, it, expect, vi } from "vitest";
import {
  supportsMetrics,
  isTcpPingEnabled,
  parseStatusHostIds,
  tcpPingThroughJumpHost,
} from "../../../hosts/metrics/helpers.js";
import { createConnectionLog } from "../../../hosts/connection-log.js";

describe("supportsMetrics", () => {
  it("supports plain ssh hosts", () => {
    expect(
      supportsMetrics({ connectionType: "ssh", authType: "password" }),
    ).toBe(true);
  });

  it("defaults missing connectionType to ssh", () => {
    expect(supportsMetrics({ authType: "key" })).toBe(true);
  });

  it("rejects non-ssh connection types", () => {
    expect(supportsMetrics({ connectionType: "rdp" })).toBe(false);
    expect(supportsMetrics({ connectionType: "vnc" })).toBe(false);
    expect(supportsMetrics({ connectionType: "telnet" })).toBe(false);
  });

  it("rejects ssh hosts that cannot run shell commands", () => {
    expect(supportsMetrics({ connectionType: "ssh", authType: "none" })).toBe(
      false,
    );
    expect(supportsMetrics({ connectionType: "ssh", authType: "opkssh" })).toBe(
      false,
    );
  });
});

describe("isTcpPingEnabled", () => {
  it("is enabled when status checks are on and tcp ping is not disabled", () => {
    expect(
      isTcpPingEnabled({ statusCheckEnabled: true, disableTcpPing: false }),
    ).toBe(true);
    expect(isTcpPingEnabled({ statusCheckEnabled: true })).toBe(true);
  });

  it("is disabled when status checks are off", () => {
    expect(isTcpPingEnabled({ statusCheckEnabled: false })).toBe(false);
  });

  it("is disabled when tcp ping is explicitly disabled", () => {
    expect(
      isTcpPingEnabled({ statusCheckEnabled: true, disableTcpPing: true }),
    ).toBe(false);
  });
});

describe("parseStatusHostIds", () => {
  it("distinguishes an unrestricted request from an empty host set", () => {
    expect(parseStatusHostIds(undefined)).toBeNull();
    expect(parseStatusHostIds("")).toEqual(new Set());
  });

  it("keeps only valid positive host IDs", () => {
    expect(parseStatusHostIds("7,2,7,-1,nope,1.5")).toEqual(new Set([7, 2]));
  });
});

describe("createConnectionLog", () => {
  it("builds a log entry without id/timestamp", () => {
    const entry = createConnectionLog("info", "connection", "Connecting", {
      hostId: 1,
    });
    expect(entry).toEqual({
      type: "info",
      stage: "connection",
      message: "Connecting",
      details: { hostId: 1 },
    });
    expect("id" in entry).toBe(false);
    expect("timestamp" in entry).toBe(false);
  });
});

describe("tcpPingThroughJumpHost", () => {
  it("reports the final destination online when forwarding succeeds", async () => {
    const stream = { destroy: vi.fn() };
    const jumpClient = {
      end: vi.fn(),
      forwardOut: vi.fn((_src, _srcPort, host, port, callback) => {
        expect(host).toBe("private.example");
        expect(port).toBe(22);
        callback(undefined, stream);
      }),
    } as unknown as Pick<Client, "forwardOut" | "end">;

    await expect(
      tcpPingThroughJumpHost(jumpClient, "private.example", 22),
    ).resolves.toBe(true);
    expect(stream.destroy).toHaveBeenCalledOnce();
    expect(jumpClient.end).toHaveBeenCalledOnce();
  });

  it("reports the final destination offline when forwarding fails", async () => {
    const jumpClient = {
      end: vi.fn(),
      forwardOut: vi.fn((_src, _srcPort, _host, _port, callback) => {
        callback(new Error("Connection refused"));
      }),
    } as unknown as Pick<Client, "forwardOut" | "end">;

    await expect(
      tcpPingThroughJumpHost(jumpClient, "private.example", 22),
    ).resolves.toBe(false);
    expect(jumpClient.end).toHaveBeenCalledOnce();
  });

  it("reports the final destination offline when forwarding times out", async () => {
    vi.useFakeTimers();
    const jumpClient = {
      end: vi.fn(),
      forwardOut: vi.fn(),
    } as unknown as Pick<Client, "forwardOut" | "end">;

    const result = tcpPingThroughJumpHost(
      jumpClient,
      "private.example",
      22,
      5000,
    );
    await vi.advanceTimersByTimeAsync(5000);

    await expect(result).resolves.toBe(false);
    expect(jumpClient.end).toHaveBeenCalledOnce();
    vi.useRealTimers();
  });
});
