import { describe, expect, it, vi } from 'vitest';

import type { ClaudeAccountUsage } from './claude-usage.js';

vi.mock('./claude-usage.js', () => ({
  getClaudeProfile: (index: number) =>
    index === 0
      ? { email: 'a@example.com', planType: 'max' }
      : { email: 'b@example.com', planType: 'pro' },
}));

import {
  buildClaudeUsageRows,
  extractCodexUsageRows,
  mergeClaudeDashboardAccounts,
} from './dashboard-usage-rows.js';
import type { StatusSnapshot } from './status-dashboard.js';

describe('extractCodexUsageRows staleness', () => {
  const freshRows = [
    { name: 'Codex1* pro', h5pct: 42, h5reset: '2h', d7pct: 65, d7reset: '3d' },
  ];
  const TEN_MIN = 600_000;

  it('returns real rows when usageRowsFetchedAt is within maxAgeMs', () => {
    const now = Date.now();
    const snapshot: StatusSnapshot = {
      serviceId: 'codex-main',
      agentType: 'codex',
      assistantName: 'test',
      updatedAt: new Date(now).toISOString(),
      entries: [],
      usageRows: freshRows,
      usageRowsFetchedAt: new Date(now - TEN_MIN + 1000).toISOString(), // 9min59s ago
    };

    const result = extractCodexUsageRows(snapshot, TEN_MIN, now);
    expect(result).toEqual(freshRows);
  });

  it('returns degraded row when usageRowsFetchedAt exceeds maxAgeMs', () => {
    const now = Date.now();
    const snapshot: StatusSnapshot = {
      serviceId: 'codex-main',
      agentType: 'codex',
      assistantName: 'test',
      updatedAt: new Date(now).toISOString(), // heartbeat is fresh
      entries: [],
      usageRows: freshRows,
      usageRowsFetchedAt: new Date(now - TEN_MIN - 1000).toISOString(), // 10min1s ago
    };

    const result = extractCodexUsageRows(snapshot, TEN_MIN, now);
    expect(result).toEqual([
      { name: 'Codex', h5pct: -1, h5reset: '', d7pct: -1, d7reset: '' },
    ]);
  });

  it('returns degraded row when usageRowsFetchedAt is missing', () => {
    const now = Date.now();
    const snapshot: StatusSnapshot = {
      serviceId: 'codex-main',
      agentType: 'codex',
      assistantName: 'test',
      updatedAt: new Date(now).toISOString(),
      entries: [],
      usageRows: freshRows,
      // usageRowsFetchedAt intentionally omitted
    };

    const result = extractCodexUsageRows(snapshot, TEN_MIN, now);
    expect(result).toEqual([
      { name: 'Codex', h5pct: -1, h5reset: '', d7pct: -1, d7reset: '' },
    ]);
  });

  it('returns empty array when snapshot is undefined', () => {
    expect(extractCodexUsageRows(undefined, TEN_MIN)).toEqual([]);
  });

  it('returns empty array when usageRows is empty', () => {
    const snapshot: StatusSnapshot = {
      serviceId: 'codex-main',
      agentType: 'codex',
      assistantName: 'test',
      updatedAt: new Date().toISOString(),
      entries: [],
      usageRows: [],
      usageRowsFetchedAt: new Date().toISOString(),
    };
    expect(extractCodexUsageRows(snapshot, TEN_MIN)).toEqual([]);
  });
});

describe('dashboard Claude usage rows', () => {
  it('keeps both Claude accounts visible when one account usage is unavailable', () => {
    const rows = buildClaudeUsageRows([
      {
        index: 0,
        masked: 'tok-a',
        isActive: true,
        isRateLimited: false,
        usage: {
          five_hour: {
            utilization: 0.4,
            resets_at: '2026-03-24T04:00:00+09:00',
          },
          seven_day: {
            utilization: 0.7,
            resets_at: '2026-03-29T04:00:00+09:00',
          },
        },
      },
      {
        index: 1,
        masked: 'tok-b',
        isActive: false,
        isRateLimited: true,
        usage: null,
      },
    ]);

    expect(rows).toHaveLength(2);
    expect(rows[0]).toMatchObject({
      name: 'Claude1* max',
      h5pct: 40,
      d7pct: 70,
    });
    expect(rows[1]).toMatchObject({
      name: 'Claude2! pro',
      h5pct: -1,
      d7pct: -1,
    });
  });

  it('marks a rate-limited account with a 429 error and does not fall back to a cached value', () => {
    const cachedAccounts: ClaudeAccountUsage[] = [
      {
        index: 0,
        masked: 'tok-a',
        isActive: true,
        isRateLimited: false,
        usage: {
          five_hour: {
            utilization: 0.4,
            resets_at: '2026-03-24T04:00:00+09:00',
          },
          seven_day: {
            utilization: 0.7,
            resets_at: '2026-03-29T04:00:00+09:00',
          },
        },
      },
    ];
    const liveAccounts: ClaudeAccountUsage[] = [
      {
        index: 0,
        masked: 'tok-a',
        isActive: true,
        isRateLimited: false,
        usage: null,
        usageRateLimited: true,
      },
    ];

    const merged = mergeClaudeDashboardAccounts(liveAccounts, cachedAccounts);
    // The 429 must win over the cached value — no stale fallback.
    expect(merged[0].usage).toBeNull();

    const rows = buildClaudeUsageRows(merged);
    expect(rows[0]).toMatchObject({ h5pct: -1, d7pct: -1, error: '429' });
  });

  it('preserves the last successful usage per account instead of collapsing to one cache entry', () => {
    const cachedAccounts: ClaudeAccountUsage[] = [
      {
        index: 0,
        masked: 'tok-a',
        isActive: false,
        isRateLimited: false,
        usage: {
          five_hour: {
            utilization: 0.25,
            resets_at: '2026-03-24T04:00:00+09:00',
          },
          seven_day: {
            utilization: 0.5,
            resets_at: '2026-03-29T04:00:00+09:00',
          },
        },
      },
      {
        index: 1,
        masked: 'tok-b',
        isActive: true,
        isRateLimited: false,
        usage: {
          five_hour: {
            utilization: 0.6,
            resets_at: '2026-03-24T06:00:00+09:00',
          },
          seven_day: {
            utilization: 0.8,
            resets_at: '2026-03-30T04:00:00+09:00',
          },
        },
      },
    ];
    const liveAccounts: ClaudeAccountUsage[] = [
      {
        index: 0,
        masked: 'tok-a',
        isActive: true,
        isRateLimited: false,
        usage: null,
      },
      {
        index: 1,
        masked: 'tok-b',
        isActive: false,
        isRateLimited: true,
        usage: {
          five_hour: {
            utilization: 0.9,
            resets_at: '2026-03-24T08:00:00+09:00',
          },
          seven_day: {
            utilization: 0.95,
            resets_at: '2026-03-31T04:00:00+09:00',
          },
        },
      },
    ];

    const merged = mergeClaudeDashboardAccounts(liveAccounts, cachedAccounts);

    expect(merged).toHaveLength(2);
    expect(merged[0]).toMatchObject({
      index: 0,
      isActive: true,
      usage: cachedAccounts[0].usage,
    });
    expect(merged[1]).toMatchObject({
      index: 1,
      isRateLimited: true,
      usage: liveAccounts[1].usage,
    });
  });
});
