import { describe, it, expect } from "vitest";
import { buildPathSummary } from "../../../src/features/packets/path-summary";
import type { PacketSummary } from "../../../src/types/api";
import { PayloadType } from "../../../src/types/enums";

const pkt = (over: Partial<PacketSummary> = {}): PacketSummary => ({
  packetHash: "AA11", payloadType: 1, payloadTypeName: "ADVERT",
  routeType: 1, routeTypeName: "FLOOD",
  firstHeardAt: 0, lastHeardAt: 0, observationCount: 1, ...over,
});
const obs = (o: object) => ({ latestObserver: { id: "o1", iata: "YVR", ...o } });

describe("buildPathSummary", () => {
  it("is n/a when latestObserver is absent", () => {
    expect(buildPathSummary(pkt()).isNa).toBe(true);
  });

  it("never claims direct reception for an unknown hop count", () => {
    const s = buildPathSummary(pkt(obs({})));
    expect(s.hopLabel).toBe("n/a");
    expect(s.isNa).toBe(true);
  });

  it("labels a literal zero hop count as direct", () => {
    const s = buildPathSummary(pkt(obs({ pathLength: { raw: "00", hashSize: 1, hopCount: 0 } })));
    expect(s.hopLabel).toBe("0 hops · direct");
    expect(s.chips).toEqual([]);
    expect(s.isNa).toBe(false);
  });

  it("is n/a when pathLength is missing even if pathBytes is present", () => {
    expect(buildPathSummary(pkt(obs({ pathBytes: "7fa4" }))).isNa).toBe(true);
  });

  it("renders hex chips split by hashSize when there is no resolved path", () => {
    const s = buildPathSummary(pkt(obs({
      pathLength: { raw: "42", hashSize: 1, hopCount: 2 }, pathBytes: "7fa4",
    })));
    expect(s.hopLabel).toBe("2 hops");
    expect(s.chips).toEqual([{ kind: "hex", label: "7f" }, { kind: "hex", label: "a4" }]);
    expect(s.overflow).toBe(0);
  });

  it("drops chips when pathBytes length disagrees with hopCount x hashSize", () => {
    const s = buildPathSummary(pkt(obs({
      pathLength: { raw: "42", hashSize: 1, hopCount: 5 }, pathBytes: "7fa4",
    })));
    expect(s.hopLabel).toBe("5 hops");
    expect(s.chips).toEqual([]);
  });

  it("renders hop count with no chips when pathBytes is absent entirely", () => {
    const s = buildPathSummary(pkt(obs({
      pathLength: { raw: "43", hashSize: 1, hopCount: 3 },
    })));
    expect(s.hopLabel).toBe("3 hops");
    expect(s.chips).toEqual([]);
    expect(s.isNa).toBe(false);
  });

  it("truncates past the hop budget and counts remaining hops", () => {
    const s = buildPathSummary(pkt(obs({
      pathLength: { raw: "4e", hashSize: 1, hopCount: 14 },
      pathBytes: "000102030405060708090a0b0c0d",
    })));
    expect(s.chips).toHaveLength(5);
    expect(s.overflow).toBe(9);
  });

  it("collapses a run of unresolved hops into one chip that costs its length", () => {
    const none = { confidence: "none" as const, nodes: [] };
    const high = { confidence: "high" as const, nodes: [{ id: "n", publicKey: "ab", name: "Raven" }] };
    const s = buildPathSummary(pkt(obs({
      pathLength: { raw: "43", hashSize: 1, hopCount: 4 }, pathBytes: "00010203",
      resolvedPath: [high, none, none, high],
    })));
    expect(s.chips).toEqual([
      { kind: "node", label: "Raven", confidence: "high" },
      { kind: "unresolved-run", count: 2 },
      { kind: "node", label: "Raven", confidence: "high" },
    ]);
    expect(s.overflow).toBe(0);
  });

  it("always shows at least one chip even when the first run exceeds the budget", () => {
    const none = { confidence: "none" as const, nodes: [] };
    const s = buildPathSummary(pkt(obs({
      pathLength: { raw: "4a", hashSize: 1, hopCount: 10 },
      pathBytes: "00010203040506070809",
      resolvedPath: Array(10).fill(none),
    })));
    expect(s.chips).toEqual([{ kind: "unresolved-run", count: 10 }]);
    expect(s.overflow).toBe(0);
  });

  it("shows hop count only for TRACE packets", () => {
    const s = buildPathSummary(pkt({
      payloadType: PayloadType.TRACE,
      ...obs({ pathLength: { raw: "43", hashSize: 1, hopCount: 3 }, pathBytes: "000102" }),
    }));
    expect(s.hopLabel).toBe("3 hops");
    expect(s.chips).toEqual([]);
  });

  it("carries resolved endpoints through", () => {
    const s = buildPathSummary(pkt(obs({
      pathLength: { raw: "41", hashSize: 1, hopCount: 1 }, pathBytes: "7f",
      resolvedSource: { confidence: "high", nodes: [{ id: "n", publicKey: "ab", name: "Salish" }] },
    })));
    expect(s.source).toEqual({ kind: "node", label: "Salish", confidence: "high" });
    expect(s.destination).toBeNull();
  });

  it("falls back to an unresolved-run chip for an endpoint with confidence none", () => {
    const s = buildPathSummary(pkt(obs({
      pathLength: { raw: "41", hashSize: 1, hopCount: 1 }, pathBytes: "7f",
      resolvedSource: { confidence: "none", nodes: [] },
    })));
    expect(s.source).toEqual({ kind: "unresolved-run", count: 1 });
  });
});
