import { describe, expect, it } from 'vitest';
import { PermissionMode, State, type StateSnapshot, type UsageEvent } from '../types.js';
import { buildUsageEvent } from '../usage-event.js';
import type { ApiUsageData } from '../usage-api.js';

function snapshot(overrides: Partial<StateSnapshot> = {}): StateSnapshot {
  return {
    state: State.IDLE,
    permissionMode: PermissionMode.DEFAULT,
    currentTool: null,
    toolInput: null,
    toolProgress: null,
    options: [],
    question: null,
    navigable: false,
    cursorIndex: 0,
    projectName: 'AgentDeck',
    modelName: null,
    effortLevel: null,
    billingType: 'unknown',
    sessionDurationSec: 0,
    inputTokens: 0,
    outputTokens: 0,
    toolCalls: 0,
    estimatedCostUsd: null,
    sessionPercent: null,
    costSpent: null,
    costLimit: null,
    resetTime: null,
    resetDate: null,
    suggestedPrompt: null,
    remoteUrl: null,
    ...overrides,
  };
}

function usage(overrides: Partial<ApiUsageData> = {}): ApiUsageData {
  return {
    fiveHourPercent: 55,
    fiveHourResetsAt: '2026-06-06T18:00:00Z',
    sevenDayPercent: 44,
    sevenDayResetsAt: '2026-06-12T18:00:00Z',
    extraUsageEnabled: true,
    extraUsageMonthlyLimit: 100,
    extraUsageUsedCredits: 12,
    extraUsageUtilization: 12,
    inferredBillingType: 'subscription',
    ...overrides,
  };
}

describe('buildUsageEvent subscription quota scoping', () => {
  it('omits Anthropic quota when the model is not yet known', () => {
    const evt = buildUsageEvent(
      snapshot({ billingType: 'subscription' }),
      usage(),
      true,
    ) as UsageEvent;

    expect(evt.fiveHourPercent).toBeUndefined();
    expect(evt.sevenDayPercent).toBeUndefined();
    expect(evt.extraUsageEnabled).toBeUndefined();
  });

  it('omits Anthropic 5h/7d quota for GLM/API-backed models', () => {
    const evt = buildUsageEvent(
      snapshot({ modelName: 'glm-5.1', billingType: 'subscription' }),
      usage(),
      true,
    ) as UsageEvent;

    expect(evt.fiveHourPercent).toBeUndefined();
    expect(evt.fiveHourResetsAt).toBeUndefined();
    expect(evt.sevenDayPercent).toBeUndefined();
    expect(evt.sevenDayResetsAt).toBeUndefined();
    expect(evt.extraUsageEnabled).toBeUndefined();
    expect(evt.oauthConnected).toBe(true);
  });

  it('keeps Anthropic 5h/7d quota on the daemon hub even with a non-Claude aggregate model', () => {
    // Regression: the daemon aggregates many agents and its `modelName` is whatever
    // agent is primary (e.g. OpenClaw "GLM-5.2"). The account-level Claude quota must
    // still broadcast so the Dashboard shows subscription usage. `aggregateSubscriptionQuota`
    // is the 12th positional arg; preAdjusted (11th) = true keeps the raw percent.
    const evt = buildUsageEvent(
      snapshot({ modelName: 'GLM-5.2 (1M)', billingType: 'subscription' }),
      usage(),
      true,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      true,
      true,
    ) as UsageEvent;

    expect(evt.fiveHourPercent).toBe(55);
    expect(evt.sevenDayPercent).toBe(44);
    expect(evt.extraUsageEnabled).toBe(true);
    expect(evt.oauthConnected).toBe(true);
  });

  it('keeps Anthropic 5h/7d quota for Claude model aliases', () => {
    const evt = buildUsageEvent(
      snapshot({ modelName: 'opus-4.6', billingType: 'subscription' }),
      usage(),
      true,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      true,
    ) as UsageEvent;

    expect(evt.fiveHourPercent).toBe(55);
    expect(evt.sevenDayPercent).toBe(44);
    expect(evt.extraUsageEnabled).toBe(true);
  });
});

describe('buildUsageEvent Codex window normalization', () => {
  const codexArgs = (codexRateLimits: unknown) =>
    [
      snapshot(),
      null, // apiUsage
      undefined, undefined, undefined, undefined, undefined, undefined,
      undefined, undefined, undefined, undefined,
      codexRateLimits,
    ] as Parameters<typeof buildUsageEvent>;

  it('marks an expired window stale and drops its resetsAt (no misleading "now")', () => {
    const expired = new Date(Date.now() - 30 * 60_000).toISOString();
    const evt = buildUsageEvent(
      ...codexArgs({ primary: { usedPercent: 67, windowMinutes: 300, resetsAt: expired } }),
    ) as UsageEvent;

    const p = evt.codexRateLimits!.primary!;
    expect(p.usedPercent).toBe(67); // last-known preserved
    expect(p.stale).toBe(true);
    expect(p.resetsAt).toBeUndefined(); // no past timestamp → no "now"
  });

  it('leaves a live window untouched', () => {
    const future = new Date(Date.now() + 2 * 3600_000).toISOString();
    const evt = buildUsageEvent(
      ...codexArgs({ secondary: { usedPercent: 12, windowMinutes: 10080, resetsAt: future } }),
    ) as UsageEvent;

    const s = evt.codexRateLimits!.secondary!;
    expect(s.usedPercent).toBe(12);
    expect(s.stale).toBeUndefined();
    expect(s.resetsAt).toBe(future);
  });

  it('treats each window independently (5h expired, 7d live)', () => {
    const expired = new Date(Date.now() - 30 * 60_000).toISOString();
    const future = new Date(Date.now() + 2 * 3600_000).toISOString();
    const evt = buildUsageEvent(
      ...codexArgs({
        primary: { usedPercent: 90, windowMinutes: 300, resetsAt: expired },
        secondary: { usedPercent: 20, windowMinutes: 10080, resetsAt: future },
      }),
    ) as UsageEvent;

    expect(evt.codexRateLimits!.primary!.stale).toBe(true);
    expect(evt.codexRateLimits!.primary!.resetsAt).toBeUndefined();
    expect(evt.codexRateLimits!.secondary!.stale).toBeUndefined();
    expect(evt.codexRateLimits!.secondary!.resetsAt).toBe(future);
  });
});
