/**
 * ccw cli show - running execution time formatting tests
 */

import { after, afterEach, before, describe, it, mock } from 'node:test';
import assert from 'node:assert/strict';
import http from 'node:http';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

const TEST_CCW_HOME = mkdtempSync(join(tmpdir(), 'ccw-cli-show-time-home-'));
process.env.CCW_DATA_DIR = TEST_CCW_HOME;

const cliCommandPath = new URL('../dist/commands/cli.js', import.meta.url).href;
const historyStorePath = new URL('../dist/tools/cli-history-store.js', import.meta.url).href;

function createConversationRecord({ id, prompt, updatedAt, durationMs = 2000 }) {
  return {
    id,
    created_at: updatedAt,
    updated_at: updatedAt,
    tool: 'codex',
    model: 'default',
    mode: 'analysis',
    category: 'user',
    total_duration_ms: durationMs,
    turn_count: 1,
    latest_status: 'success',
    turns: [
      {
        turn: 1,
        timestamp: updatedAt,
        prompt,
        duration_ms: durationMs,
        status: 'success',
        exit_code: 0,
        output: {
          stdout: 'saved output',
          stderr: '',
          truncated: false,
          cached: false,
        },
      },
    ],
  };
}

function stubActiveExecutionsResponse(executions) {
  mock.method(http, 'request', (_options, callback) => {
    const payload = JSON.stringify({ executions });
    const res = {
      on(event, handler) {
        if (event === 'data') {
          handler(Buffer.from(payload, 'utf8'));
        }
        if (event === 'end') {
          handler();
        }
        return res;
      }
    };

    if (callback) {
      callback(res);
    }

    const req = {
      on() { return req; },
      write() {},
      end() {},
      destroy() {},
    };
    return req;
  });
}

describe('ccw cli show running time formatting', async () => {
  let cliModule;
  let historyStoreModule;

  before(async () => {
    cliModule = await import(cliCommandPath);
    historyStoreModule = await import(historyStorePath);
  });

  afterEach(() => {
    mock.restoreAll();
    try {
      historyStoreModule?.closeAllStores?.();
    } catch {
      // ignore
    }
  });

  after(() => {
    try {
      historyStoreModule?.closeAllStores?.();
    } catch {
      // ignore
    }
    rmSync(TEST_CCW_HOME, { recursive: true, force: true });
  });

  it('formats running time with the same humanized style as history rows', async () => {
    const now = 1_741_392_000_000;
    stubActiveExecutionsResponse([
      {
        id: 'EXEC-RUN-125S',
        tool: 'codex',
        mode: 'analysis',
        status: 'running',
        prompt: 'long task',
        startTime: now - 125_000,
        output: ''
      }
    ]);

    mock.method(Date, 'now', () => now);

    const logs = [];
    mock.method(console, 'log', (...args) => {
      logs.push(args.map(String).join(' '));
    });
    mock.method(console, 'error', () => {});

    await cliModule.cliCommand('show', [], {});

    const rendered = logs.join('\n');
    assert.match(rendered, /2m ago/);
    assert.match(rendered, /2m 5s\.\.\./);
    assert.doesNotMatch(rendered, /125s ago/);
  });

  it('normalizes second-based string timestamps for running executions', async () => {
    const now = 1_741_392_000_000;
    const startTimeSeconds = String(Math.floor((now - 3_600_000) / 1000));

    stubActiveExecutionsResponse([
      {
        id: 'EXEC-RUN-1H',
        tool: 'gemini',
        mode: 'write',
        status: 'running',
        prompt: 'hour task',
        startTime: startTimeSeconds,
        output: ''
      }
    ]);

    mock.method(Date, 'now', () => now);

    const logs = [];
    mock.method(console, 'log', (...args) => {
      logs.push(args.map(String).join(' '));
    });
    mock.method(console, 'error', () => {});

    await cliModule.cliCommand('show', [], {});

    const rendered = logs.join('\n');
    assert.match(rendered, /1h ago/);
    assert.match(rendered, /1h\.\.\./);
  });

  it('lists executions from other registered projects in show output', async () => {
    const projectRoot = mkdtempSync(join(tmpdir(), 'ccw-cli-show-cross-project-'));
    const unrelatedCwd = mkdtempSync(join(tmpdir(), 'ccw-cli-show-cross-cwd-'));
    const previousCwd = process.cwd();

    try {
      process.chdir(unrelatedCwd);
      const store = new historyStoreModule.CliHistoryStore(projectRoot);
      store.saveConversation(createConversationRecord({
        id: 'EXEC-CROSS-PROJECT-SHOW',
        prompt: 'cross project show prompt',
        updatedAt: new Date('2025-02-02T00:00:00.000Z').toISOString(),
        durationMs: 1800,
      }));
      store.close();

      stubActiveExecutionsResponse([]);

      const logs = [];
      mock.method(console, 'log', (...args) => {
        logs.push(args.map(String).join(' '));
      });
      mock.method(console, 'error', () => {});

      await cliModule.cliCommand('show', [], {});

      const rendered = logs.join('\n');
      assert.match(rendered, /EXEC-CROSS-PROJECT-SHOW/);
      assert.match(rendered, /cross project show prompt/);
    } finally {
      process.chdir(previousCwd);
      rmSync(projectRoot, { recursive: true, force: true });
      rmSync(unrelatedCwd, { recursive: true, force: true });
    }
  });

  it('suppresses stale running rows when saved history is newer than the active start time', async () => {
    const projectRoot = mkdtempSync(join(tmpdir(), 'ccw-cli-show-stale-project-'));
    const previousCwd = process.cwd();
    const now = 1_741_392_000_000;
    const savedUpdatedAt = new Date(now - 5_000).toISOString();

    try {
      process.chdir(projectRoot);
      const store = new historyStoreModule.CliHistoryStore(projectRoot);
      store.saveConversation(createConversationRecord({
        id: 'EXEC-STALE-RUNNING',
        prompt: 'HISTORY PROMPT SHOULD WIN',
        updatedAt: savedUpdatedAt,
        durationMs: 2300,
      }));
      store.close();

      stubActiveExecutionsResponse([
        {
          id: 'EXEC-STALE-RUNNING',
          tool: 'codex',
          mode: 'analysis',
          status: 'running',
          prompt: 'ACTIVE PROMPT SHOULD BE HIDDEN',
          startTime: now - 60_000,
          output: ''
        }
      ]);

      mock.method(Date, 'now', () => now);

      const logs = [];
      mock.method(console, 'log', (...args) => {
        logs.push(args.map(String).join(' '));
      });
      mock.method(console, 'error', () => {});

      await cliModule.cliCommand('show', [], {});

      const rendered = logs.join('\n');
      assert.match(rendered, /HISTORY PROMPT SHOULD WIN/);
      assert.doesNotMatch(rendered, /ACTIVE PROMPT SHOULD BE HIDDEN/);
      assert.match(rendered, /2\.3s/);
    } finally {
      process.chdir(previousCwd);
      rmSync(projectRoot, { recursive: true, force: true });
    }
  });

  it('keeps active running rows when saved history is older than the active start time \(resume-safe\)', async () => {
    const projectRoot = mkdtempSync(join(tmpdir(), 'ccw-cli-show-resume-project-'));
    const previousCwd = process.cwd();
    const now = 1_741_392_000_000;
    const savedUpdatedAt = new Date(now - 120_000).toISOString();

    try {
      process.chdir(projectRoot);
      const store = new historyStoreModule.CliHistoryStore(projectRoot);
      store.saveConversation(createConversationRecord({
        id: 'EXEC-RESUME-RUNNING',
        prompt: 'OLD HISTORY PROMPT',
        updatedAt: savedUpdatedAt,
        durationMs: 1800,
      }));
      store.close();

      stubActiveExecutionsResponse([
        {
          id: 'EXEC-RESUME-RUNNING',
          tool: 'codex',
          mode: 'analysis',
          status: 'running',
          prompt: 'ACTIVE RESUME PROMPT',
          startTime: now - 30_000,
          output: ''
        }
      ]);

      mock.method(Date, 'now', () => now);

      const logs = [];
      mock.method(console, 'log', (...args) => {
        logs.push(args.map(String).join(' '));
      });
      mock.method(console, 'error', () => {});

      await cliModule.cliCommand('show', [], {});

      const rendered = logs.join('\n');
      assert.match(rendered, /ACTIVE RESUME PROMPT/);
      assert.doesNotMatch(rendered, /OLD HISTORY PROMPT/);
      assert.match(rendered, /just now/);
    } finally {
      process.chdir(previousCwd);
      rmSync(projectRoot, { recursive: true, force: true });
    }
  });
});
