import { describe, expect, it } from "vitest";
import {
  createUsageAccumulator,
  mergeUsageIntoAccumulator,
  resolveLastCallUsage,
  toLastCallUsage,
  toNormalizedUsage,
} from "./usage-accumulator.js";

describe("usage-accumulator", () => {
  describe("mergeUsageIntoAccumulator", () => {
    it("accumulates usage across multiple API calls", () => {
      const acc = createUsageAccumulator();

      mergeUsageIntoAccumulator(acc, {
        input: 100,
        output: 50,
        cacheRead: 80_000,
        cacheWrite: 5_000,
        total: 85_150,
      });
      mergeUsageIntoAccumulator(acc, {
        input: 120,
        output: 30,
        cacheRead: 82_000,
        cacheWrite: 0,
        total: 82_150,
      });
      mergeUsageIntoAccumulator(acc, {
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: 0,
        total: 84_190,
      });

      expect(acc.input).toBe(370);
      expect(acc.output).toBe(120);
      expect(acc.cacheRead).toBe(246_000);
      expect(acc.cacheWrite).toBe(5_000);
      expect(acc.total).toBe(251_490);
    });

    it("stores the exact final call snapshot", () => {
      const acc = createUsageAccumulator();

      mergeUsageIntoAccumulator(acc, {
        input: 100,
        output: 50,
        cacheRead: 80_000,
        cacheWrite: 5_000,
        total: 85_150,
      });
      mergeUsageIntoAccumulator(acc, {
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: 0,
        total: 84_190,
      });

      expect(acc.lastInput).toBe(150);
      expect(acc.lastOutput).toBe(40);
      expect(acc.lastCacheRead).toBe(84_000);
      expect(acc.lastCacheWrite).toBe(0);
      expect(acc.lastTotal).toBe(84_190);
    });

    it("ignores undefined or zero-only usage", () => {
      const acc = createUsageAccumulator();

      mergeUsageIntoAccumulator(acc, undefined);
      mergeUsageIntoAccumulator(acc, {
        input: 0,
        output: 0,
        cacheRead: 0,
        cacheWrite: 0,
        total: 0,
      });

      expect(acc).toEqual(createUsageAccumulator());
    });
  });

  describe("toNormalizedUsage", () => {
    it("returns undefined for an empty accumulator", () => {
      expect(toNormalizedUsage(createUsageAccumulator())).toBeUndefined();
    });

    it("returns accumulated totals for billing", () => {
      const acc = createUsageAccumulator();

      mergeUsageIntoAccumulator(acc, {
        input: 100,
        output: 50,
        cacheRead: 80_000,
        cacheWrite: 5_000,
      });
      mergeUsageIntoAccumulator(acc, {
        input: 120,
        output: 30,
        cacheRead: 82_000,
        cacheWrite: 0,
      });
      mergeUsageIntoAccumulator(acc, {
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: 0,
      });

      expect(toNormalizedUsage(acc)).toEqual({
        input: 370,
        output: 120,
        cacheRead: 246_000,
        cacheWrite: 5_000,
        total: 251_490,
      });
    });

    it("omits zero fields", () => {
      const acc = createUsageAccumulator();
      mergeUsageIntoAccumulator(acc, { input: 100, output: 50 });

      expect(toNormalizedUsage(acc)).toEqual({
        input: 100,
        output: 50,
        cacheRead: undefined,
        cacheWrite: undefined,
        total: 150,
      });
    });
  });

  describe("toLastCallUsage", () => {
    it("returns the exact final call snapshot", () => {
      const acc = createUsageAccumulator();

      mergeUsageIntoAccumulator(acc, {
        input: 100,
        output: 50,
        cacheRead: 80_000,
        cacheWrite: 5_000,
        total: 85_150,
      });
      mergeUsageIntoAccumulator(acc, {
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: 0,
        total: 84_190,
      });

      expect(toLastCallUsage(acc)).toEqual({
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: undefined,
        total: 84_190,
      });
    });

    it("returns undefined for an empty accumulator", () => {
      expect(toLastCallUsage(createUsageAccumulator())).toBeUndefined();
    });
  });

  describe("resolveLastCallUsage", () => {
    it("prefers raw assistant usage when present", () => {
      const acc = createUsageAccumulator();
      mergeUsageIntoAccumulator(acc, {
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: 0,
        total: 84_190,
      });

      expect(
        resolveLastCallUsage(
          {
            inputTokens: 99,
            outputTokens: 12,
            cache_read_input_tokens: 456,
            cache_creation_input_tokens: 3,
            totalTokens: 570,
          },
          acc,
        ),
      ).toEqual({
        input: 99,
        output: 12,
        cacheRead: 456,
        cacheWrite: 3,
        total: 570,
      });
    });

    it("falls back to the accumulator when assistant usage is missing", () => {
      const acc = createUsageAccumulator();
      mergeUsageIntoAccumulator(acc, {
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: 0,
        total: 84_190,
      });

      expect(resolveLastCallUsage(undefined, acc)).toEqual({
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: undefined,
        total: 84_190,
      });
    });

    it("falls back when assistant usage exists but is unusable", () => {
      const acc = createUsageAccumulator();
      mergeUsageIntoAccumulator(acc, {
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: 0,
        total: 84_190,
      });

      expect(resolveLastCallUsage({ responseId: "abc" } as never, acc)).toEqual({
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: undefined,
        total: 84_190,
      });
    });

    it("keeps an explicit zero-usage raw snapshot instead of falling back", () => {
      const acc = createUsageAccumulator();
      mergeUsageIntoAccumulator(acc, {
        input: 150,
        output: 40,
        cacheRead: 84_000,
        cacheWrite: 0,
        total: 84_190,
      });

      expect(
        resolveLastCallUsage(
          {
            input: 0,
            output: 0,
            cacheRead: 0,
            cacheWrite: 0,
            total: 0,
          },
          acc,
        ),
      ).toEqual({
        input: 0,
        output: 0,
        cacheRead: 0,
        cacheWrite: 0,
        total: 0,
      });
    });
  });
});
