import { test } from 'node:test';
import assert from 'node:assert/strict';
import { formatSessionDuration, main } from '../dist/index.js';

test('formatSessionDuration returns empty string without session start', () => {
  assert.equal(formatSessionDuration(undefined, () => 0), '');
});

test('formatSessionDuration formats sub-minute and minute durations', () => {
  const start = new Date(0);
  assert.equal(formatSessionDuration(start, () => 30 * 1000), '<1m');
  assert.equal(formatSessionDuration(start, () => 5 * 60 * 1000), '5m');
});

test('formatSessionDuration formats hour durations', () => {
  const start = new Date(0);
  assert.equal(formatSessionDuration(start, () => 2 * 60 * 60 * 1000 + 5 * 60 * 1000), '2h 5m');
});

test('formatSessionDuration uses Date.now by default', () => {
  const originalNow = Date.now;
  Date.now = () => 60000;
  try {
    const result = formatSessionDuration(new Date(0));
    assert.equal(result, '1m');
  } finally {
    Date.now = originalNow;
  }
});

test('main logs an error when dependencies throw', async () => {
  const logs = [];
  await main({
    readStdin: async () => {
      throw new Error('boom');
    },
    parseTranscript: async () => ({ tools: [], agents: [], todos: [] }),
    countConfigs: async () => ({ claudeMdCount: 0, rulesCount: 0, mcpCount: 0, hooksCount: 0 }),
    getGitBranch: async () => null,
    getUsage: async () => null,
    render: () => {},
    now: () => Date.now(),
    log: (...args) => logs.push(args.join(' ')),
  });

  assert.ok(logs.some((line) => line.includes('[claude-hud] Error:')));
});

test('main logs unknown error for non-Error throws', async () => {
  const logs = [];
  await main({
    readStdin: async () => {
      throw 'boom';
    },
    parseTranscript: async () => ({ tools: [], agents: [], todos: [] }),
    countConfigs: async () => ({ claudeMdCount: 0, rulesCount: 0, mcpCount: 0, hooksCount: 0 }),
    getGitBranch: async () => null,
    getUsage: async () => null,
    render: () => {},
    now: () => Date.now(),
    log: (...args) => logs.push(args.join(' ')),
  });

  assert.ok(logs.some((line) => line.includes('Unknown error')));
});

test('index entrypoint runs when executed directly', async () => {
  const originalArgv = [...process.argv];
  const originalIsTTY = process.stdin.isTTY;
  const originalLog = console.log;
  const logs = [];

  try {
    const moduleUrl = new URL('../dist/index.js', import.meta.url);
    process.argv[1] = new URL(moduleUrl).pathname;
    Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
    console.log = (...args) => logs.push(args.join(' '));
    await import(`${moduleUrl}?entry=${Date.now()}`);
  } finally {
    console.log = originalLog;
    process.argv = originalArgv;
    Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true });
  }

  assert.ok(logs.some((line) => line.includes('[claude-hud] Initializing...')));
});

test('main executes the happy path with default dependencies', async () => {
  const originalNow = Date.now;
  Date.now = () => 60000;
  let renderedContext;

  try {
    await main({
      readStdin: async () => ({
        model: { display_name: 'Opus' },
        context_window: { context_window_size: 100, current_usage: { input_tokens: 90 } },
      }),
      parseTranscript: async () => ({ tools: [], agents: [], todos: [], sessionStart: new Date(0) }),
      countConfigs: async () => ({ claudeMdCount: 0, rulesCount: 0, mcpCount: 0, hooksCount: 0 }),
      getGitBranch: async () => null,
      getUsage: async () => null,
      render: (ctx) => {
        renderedContext = ctx;
      },
    });
  } finally {
    Date.now = originalNow;
  }

  assert.equal(renderedContext?.sessionDuration, '1m');
});

test('main includes git status in render context', async () => {
  let renderedContext;

  await main({
    readStdin: async () => ({
      model: { display_name: 'Opus' },
      context_window: { context_window_size: 100, current_usage: { input_tokens: 10 } },
      cwd: '/some/path',
    }),
    parseTranscript: async () => ({ tools: [], agents: [], todos: [] }),
    countConfigs: async () => ({ claudeMdCount: 0, rulesCount: 0, mcpCount: 0, hooksCount: 0 }),
    getGitStatus: async () => ({ branch: 'feature/test', isDirty: false, ahead: 0, behind: 0 }),
    getUsage: async () => null,
    loadConfig: async () => ({
      lineLayout: 'compact',
      showSeparators: false,
      pathLevels: 1,
      gitStatus: { enabled: true, showDirty: true, showAheadBehind: false, showFileStats: false },
      display: { showModel: true, showContextBar: true, showConfigCounts: true, showDuration: true, showTokenBreakdown: true, showUsage: true, showTools: true, showAgents: true, showTodos: true, autocompactBuffer: 'enabled', usageThreshold: 0, environmentThreshold: 0 },
    }),
    render: (ctx) => {
      renderedContext = ctx;
    },
  });

  assert.equal(renderedContext?.gitStatus?.branch, 'feature/test');
});

test('main includes usageData in render context', async () => {
  let renderedContext;
  const mockUsageData = {
    planName: 'Max',
    fiveHour: 50,
    sevenDay: 25,
    fiveHourResetAt: null,
    sevenDayResetAt: null,
    limitReached: false,
  };

  await main({
    readStdin: async () => ({
      model: { display_name: 'Opus' },
      context_window: { context_window_size: 100, current_usage: { input_tokens: 10 } },
    }),
    parseTranscript: async () => ({ tools: [], agents: [], todos: [] }),
    countConfigs: async () => ({ claudeMdCount: 0, rulesCount: 0, mcpCount: 0, hooksCount: 0 }),
    getGitBranch: async () => null,
    getUsage: async () => mockUsageData,
    render: (ctx) => {
      renderedContext = ctx;
    },
  });

  assert.deepEqual(renderedContext?.usageData, mockUsageData);
});
