import { describe, expect, it } from "vitest";
import type { SessionState } from "../logging/diagnostic-session-state.js";
import {
  calculateBackoffMs,
  getCommandPollSuggestion,
  pruneStaleCommandPolls,
  recordCommandPoll,
  resetCommandPollCount,
} from "./command-poll-backoff.js";

describe("command-poll-backoff", () => {
  describe("calculateBackoffMs", () => {
    it("returns 5s for first poll", () => {
      expect(calculateBackoffMs(0)).toBe(5000);
    });

    it("returns 10s for second poll", () => {
      expect(calculateBackoffMs(1)).toBe(10000);
    });

    it("returns 30s for third poll", () => {
      expect(calculateBackoffMs(2)).toBe(30000);
    });

    it("returns 60s for fourth and subsequent polls (capped)", () => {
      expect(calculateBackoffMs(3)).toBe(60000);
      expect(calculateBackoffMs(4)).toBe(60000);
      expect(calculateBackoffMs(10)).toBe(60000);
      expect(calculateBackoffMs(100)).toBe(60000);
    });
  });

  describe("recordCommandPoll", () => {
    it("returns 5s on first no-output poll", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
      };
      const retryMs = recordCommandPoll(state, "cmd-123", false);
      expect(retryMs).toBe(5000);
      expect(state.commandPollCounts?.get("cmd-123")?.count).toBe(0); // First poll = index 0
    });

    it("increments count and increases backoff on consecutive no-output polls", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
      };

      expect(recordCommandPoll(state, "cmd-123", false)).toBe(5000); // count=0 -> 5s
      expect(recordCommandPoll(state, "cmd-123", false)).toBe(10000); // count=1 -> 10s
      expect(recordCommandPoll(state, "cmd-123", false)).toBe(30000); // count=2 -> 30s
      expect(recordCommandPoll(state, "cmd-123", false)).toBe(60000); // count=3 -> 60s
      expect(recordCommandPoll(state, "cmd-123", false)).toBe(60000); // count=4 -> 60s (capped)

      expect(state.commandPollCounts?.get("cmd-123")?.count).toBe(4); // 5 polls = index 4
    });

    it("resets count when poll returns new output", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
      };

      recordCommandPoll(state, "cmd-123", false);
      recordCommandPoll(state, "cmd-123", false);
      recordCommandPoll(state, "cmd-123", false);
      expect(state.commandPollCounts?.get("cmd-123")?.count).toBe(2); // 3 polls = index 2

      // New output resets count
      const retryMs = recordCommandPoll(state, "cmd-123", true);
      expect(retryMs).toBe(5000); // Back to first poll delay
      expect(state.commandPollCounts?.get("cmd-123")?.count).toBe(0);
    });

    it("tracks different commands independently", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
      };

      recordCommandPoll(state, "cmd-1", false);
      recordCommandPoll(state, "cmd-1", false);
      recordCommandPoll(state, "cmd-2", false);

      expect(state.commandPollCounts?.get("cmd-1")?.count).toBe(1); // 2 polls = index 1
      expect(state.commandPollCounts?.get("cmd-2")?.count).toBe(0); // 1 poll = index 0
    });
  });

  describe("getCommandPollSuggestion", () => {
    it("returns undefined for untracked command", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
      };
      expect(getCommandPollSuggestion(state, "unknown")).toBeUndefined();
    });

    it("returns current backoff for tracked command", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
      };

      recordCommandPoll(state, "cmd-123", false);
      recordCommandPoll(state, "cmd-123", false);

      expect(getCommandPollSuggestion(state, "cmd-123")).toBe(10000);
    });
  });

  describe("resetCommandPollCount", () => {
    it("removes command from tracking", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
      };

      recordCommandPoll(state, "cmd-123", false);
      expect(state.commandPollCounts?.has("cmd-123")).toBe(true);

      resetCommandPollCount(state, "cmd-123");
      expect(state.commandPollCounts?.has("cmd-123")).toBe(false);
    });

    it("is safe to call on untracked command", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
      };

      expect(() => resetCommandPollCount(state, "unknown")).not.toThrow();
    });
  });

  describe("pruneStaleCommandPolls", () => {
    it("removes polls older than maxAge", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "processing",
        queueDepth: 0,
        commandPollCounts: new Map([
          ["cmd-old", { count: 5, lastPollAt: Date.now() - 7200000 }], // 2 hours ago
          ["cmd-new", { count: 3, lastPollAt: Date.now() - 1000 }], // 1 second ago
        ]),
      };

      pruneStaleCommandPolls(state, 3600000); // 1 hour max age

      expect(state.commandPollCounts?.has("cmd-old")).toBe(false);
      expect(state.commandPollCounts?.has("cmd-new")).toBe(true);
    });

    it("handles empty state gracefully", () => {
      const state: SessionState = {
        lastActivity: Date.now(),
        state: "idle",
        queueDepth: 0,
      };

      expect(() => pruneStaleCommandPolls(state)).not.toThrow();
    });
  });
});
