import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest';
import {
  truncate, jsonStringify, jsonChunk, buildToolText,
  postSessionMessage, SIDE_EFFECT_TOOLS,
} from '../src/index.js';
import { saveEnv, resetEnv, stubEnv, createMockFetch } from './helpers.js';
import { setOpencodeClient } from '../src/index.js';

beforeAll(() => saveEnv());

describe('TestTruncate', () => {
  it('returns text unchanged when within limit', () => {
    expect(truncate('short', 100)).toBe('short');
  });

  it('truncates and appends omission notice', () => {
    const text = 'a'.repeat(15_000);
    const result = truncate(text, 10_000);
    expect(result.length).toBeLessThan(15_000);
    expect(result).toContain('more chars omitted');
  });

  it('uses default MAX_CHUNK when maxLen omitted', () => {
    const text = 'x'.repeat(20_000);
    const result = truncate(text);
    expect(result.length).toBeLessThan(20_000);
  });
});

describe('TestJsonStringify', () => {
  it('handles bigint', () => {
    expect(jsonStringify({ n: BigInt(9007199254740991) })).toBe('{"n":"9007199254740991"}');
  });

  it('handles RegExp', () => {
    expect(jsonStringify({ r: /test/g })).toBe('{"r":"/test/g"}');
  });

  it('handles Date', () => {
    const d = new Date('2025-01-01T00:00:00.000Z');
    const result = jsonStringify({ d });
    // The custom replacer converts Date to String(d)
    expect(result).toContain('2025-01-01');
  });

  it('passes through normal values', () => {
    expect(jsonStringify({ a: 1, b: 'hello' })).toBe('{"a":1,"b":"hello"}');
  });
});

describe('TestBuildToolText', () => {
  it('includes tool name', () => {
    const text = buildToolText('Bash', {}, {});
    expect(text).toContain('[PostToolUse] Bash');
  });

  it('includes input and response', () => {
    const text = buildToolText('Read', { file_path: '/foo' }, { content: 'bar' });
    expect(text).toContain('tool_input:');
    expect(text).toContain('tool_response:');
    expect(text).toContain('/foo');
  });

  it('handles null values', () => {
    const text = buildToolText('Edit', null, null);
    expect(text).toContain('[PostToolUse] Edit');
    expect(text).toContain('null');
  });

  it('truncates large input', () => {
    const big = 'x'.repeat(20_000);
    const text = buildToolText('Bash', big, 'ok');
    expect(text.length).toBeLessThan(25_000);
  });
});

describe('TestPostSessionMessage', () => {
  beforeEach(() => {
    resetEnv();
    setOpencodeClient(null);
  });

  it('returns true on successful POST', async () => {
    stubEnv({ OG_MEMORY_URL: 'http://localhost:8090' });
    vi.stubGlobal('fetch', createMockFetch({
      ok: true, status: 200,
      json: () => Promise.resolve({ ok: true }),
    }));

    const result = await postSessionMessage('sess-1', 'tool', 'content');
    expect(result).toBe(true);
  });

  it('returns false on POST failure', async () => {
    stubEnv({ OG_MEMORY_URL: 'http://localhost:8090' });
    vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('ECONNREFUSED')));

    const result = await postSessionMessage('sess-1', 'tool', 'content');
    expect(result).toBe(false);
  });
});

describe('TestToolExecuteAfterHookLogic', () => {
  beforeEach(() => {
    resetEnv();
    setOpencodeClient(null);
    vi.restoreAllMocks();
  });

  it('skips non-side-effect tools (Read/Glob/Grep)', () => {
    expect(SIDE_EFFECT_TOOLS.has('Read')).toBe(false);
    expect(SIDE_EFFECT_TOOLS.has('Glob')).toBe(false);
    expect(SIDE_EFFECT_TOOLS.has('Grep')).toBe(false);
  });

  it('includes side-effect tools (Bash/Write/Edit)', () => {
    expect(SIDE_EFFECT_TOOLS.has('Bash')).toBe(true);
    expect(SIDE_EFFECT_TOOLS.has('Write')).toBe(true);
    expect(SIDE_EFFECT_TOOLS.has('Edit')).toBe(true);
  });

  it('POSTs for side-effect tools via postSessionMessage', async () => {
    stubEnv({ OG_MEMORY_URL: 'http://localhost:8090' });
    vi.stubGlobal('fetch', createMockFetch({
      ok: true, status: 200,
      json: () => Promise.resolve({ ok: true }),
    }));
    const sessionId = 'sess-1';
    const toolName = 'Bash';
    const text = buildToolText(toolName, { command: 'ls' }, 'file.txt');
    const result = await postSessionMessage(sessionId, 'tool', text);
    expect(result).toBe(true);
    expect(fetch).toHaveBeenCalled();
  });
});