import type { ChatMessage } from '@genesislcap/foundation-ai';
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import { sumCosts } from './sum-costs';

const userMsg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
  role: 'user',
  content: '',
  ...overrides,
});

const assistantMsg = (overrides: Partial<ChatMessage> = {}): ChatMessage => ({
  role: 'assistant',
  content: '',
  ...overrides,
});

const suite = createLogicSuite('sumCosts');

suite('returns 0 for an empty message list', () => {
  assert.is(sumCosts([]), 0);
});

suite('returns 0 when no message carries cost (e.g. all from a no-usage provider)', () => {
  assert.is(sumCosts([userMsg(), assistantMsg({ content: 'hi' })]), 0);
});

suite('sums top-level message costs', () => {
  // Use integer-cents arithmetic to avoid float-equality flake; the production
  // path only ever adds floats here, so any rounding lives in display, not sum.
  const cost = sumCosts([
    assistantMsg({ cost: 0.5 }),
    assistantMsg({ cost: 1.5 }),
    userMsg(), // no cost — ignored
  ]);
  assert.is(cost, 2);
});

suite('includes sub-agent trace costs attached to a tool call', () => {
  const parentToolCallMsg = assistantMsg({
    cost: 0.1,
    toolCalls: [
      {
        id: 'tc1',
        name: 'spawn_sub',
        args: {},
        subAgentTrace: [assistantMsg({ cost: 0.5 }), assistantMsg({ cost: 0.4 })],
      },
    ],
  });
  // 0.1 (parent) + 0.5 + 0.4 (sub) = 1.0
  assert.is(sumCosts([parentToolCallMsg]), 1);
});

suite('recurses into nested sub-agent traces', () => {
  const innerTrace: ChatMessage[] = [assistantMsg({ cost: 0.25 })];
  const middleTrace: ChatMessage[] = [
    assistantMsg({
      cost: 0.5,
      toolCalls: [{ id: 'inner', name: 'spawn_inner', args: {}, subAgentTrace: innerTrace }],
    }),
  ];
  const top = assistantMsg({
    cost: 1,
    toolCalls: [{ id: 'middle', name: 'spawn_middle', args: {}, subAgentTrace: middleTrace }],
  });
  // 1 + 0.5 + 0.25 = 1.75
  assert.is(sumCosts([top]), 1.75);
});

suite('handles a mix of tool calls with and without subAgentTrace', () => {
  const cost = sumCosts([
    assistantMsg({
      cost: 1,
      toolCalls: [
        { id: 'a', name: 'plain_tool', args: {} }, // no trace — skipped
        {
          id: 'b',
          name: 'sub_tool',
          args: {},
          subAgentTrace: [assistantMsg({ cost: 2 })],
        },
      ],
    }),
  ]);
  assert.is(cost, 3);
});

suite('ignores missing cost fields inside an otherwise-populated trace', () => {
  const cost = sumCosts([
    assistantMsg({
      toolCalls: [
        {
          id: 'tc',
          name: 'sub',
          args: {},
          subAgentTrace: [
            userMsg(), // role=user, no cost
            assistantMsg({ content: 'partial', cost: 0.3 }),
            assistantMsg({ content: 'unbilled' }), // no cost field
          ],
        },
      ],
    }),
  ]);
  assert.is(cost, 0.3);
});

suite('adds widget-reported externalCostUsd alongside provider cost', () => {
  // A message can carry both: provider LLM `cost` and a widget's `externalCostUsd`.
  const cost = sumCosts([
    assistantMsg({ cost: 0.5 }), // provider only
    assistantMsg({ externalCostUsd: 0.25 }), // external only (e.g. an interaction message)
    assistantMsg({ cost: 0.1, externalCostUsd: 0.4 }), // both
    userMsg(), // neither — ignored
  ]);
  // 0.5 + 0.25 + 0.1 + 0.4 = 1.25
  assert.is(cost, 1.25);
});

suite('includes externalCostUsd inside a sub-agent trace', () => {
  const cost = sumCosts([
    assistantMsg({
      toolCalls: [
        {
          id: 'tc',
          name: 'sub',
          args: {},
          subAgentTrace: [assistantMsg({ cost: 0.5, externalCostUsd: 0.5 })],
        },
      ],
    }),
  ]);
  assert.is(cost, 1);
});

suite.run();
