import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest';
import {
  buildAdditionalContext, extractPromptText, MAX_CONTEXT_CHARS,
  MIN_PROMPT_LEN, getApiBaseUrl, baseCtx, postJson, COMPOSE_TIMEOUT,
} from '../src/index.js';
import { saveEnv, resetEnv, stubEnv, createMockFetch } from './helpers.js';
import { setOpencodeClient } from '../src/index.js';

beforeAll(() => saveEnv());

describe('TestBuildAdditionalContext', () => {
  it('includes identity and evidence', () => {
    const result = buildAdditionalContext({ identityContext: 'I am Alice', retrievedEvidence: 'Past: X' });
    expect(result).toContain('I am Alice');
    expect(result).toContain('Past: X');
    expect(result!.startsWith('[oG-Memory]')).toBe(true);
  });

  it('returns null when both empty', () => {
    expect(buildAdditionalContext({})).toBeNull();
    expect(buildAdditionalContext({ identityContext: '', retrievedEvidence: '' })).toBeNull();
  });

  it('works with only identity', () => {
    const result = buildAdditionalContext({ identityContext: 'profile data' });
    expect(result).not.toBeNull();
    expect(result).toContain('profile data');
  });

  it('truncates to MAX_CONTEXT_CHARS', () => {
    const result = buildAdditionalContext({ identityContext: 'x'.repeat(20_000) });
    expect(result!.length).toBeLessThanOrEqual(MAX_CONTEXT_CHARS + '[oG-Memory]\n'.length);
  });
});

describe('TestExtractPromptText', () => {
  it('extracts text parts', () => {
    const output = {
      message: { role: 'user' },
      parts: [
        { type: 'text', text: 'hello' },
        { type: 'tool', callID: 'x', tool: 'Bash', state: { status: 'completed' } },
        { type: 'text', text: 'world' },
      ],
    };
    expect(extractPromptText(output as any)).toBe('hello\nworld');
  });

  it('returns empty string when no text parts', () => {
    const output = {
      message: { role: 'user' },
      parts: [{ type: 'tool', callID: 'x', tool: 'Bash', state: {} }],
    };
    expect(extractPromptText(output as any)).toBe('');
  });
});

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

  it('skips short prompts (< MIN_PROMPT_LEN)', () => {
    const prompt = extractPromptText({
      message: { role: 'user' },
      parts: [{ type: 'text', text: 'hi' }],
    } as any);
    expect(prompt.length).toBeLessThan(MIN_PROMPT_LEN);
  });

  it('skips slash commands', () => {
    const prompt = extractPromptText({
      message: { role: 'user' },
      parts: [{ type: 'text', text: '/og-compose test' }],
    } as any);
    expect(prompt.startsWith('/')).toBe(true);
  });

  it('POSTs compose and builds additionalContext on success', async () => {
    stubEnv({ OG_MEMORY_URL: 'http://localhost:8090' });
    const composeResponse = { identityContext: 'Alice is a dev', retrievedEvidence: '' };
    vi.stubGlobal('fetch', createMockFetch({
      ok: true, status: 200,
      json: () => Promise.resolve(composeResponse),
    }));

    const sessionId = 's1';
    const prompt = 'what did we discuss';
    const url = `${getApiBaseUrl()}/api/v1/compose`;
    const body = { ...baseCtx(sessionId), prompt };
    const data = await postJson(url, body, COMPOSE_TIMEOUT) as { identityContext?: string; retrievedEvidence?: string } | null;
    expect(data).not.toBeNull();

    const additional = buildAdditionalContext(data!);
    expect(additional).not.toBeNull();
    expect(additional).toContain('[oG-Memory]');
    expect(additional).toContain('Alice is a dev');
  });

  it('returns null additionalContext when compose returns empty', async () => {
    stubEnv({ OG_MEMORY_URL: 'http://localhost:8090' });
    vi.stubGlobal('fetch', createMockFetch({
      ok: true, status: 200,
      json: () => Promise.resolve({ identityContext: '', retrievedEvidence: '' }),
    }));

    const sessionId = 's1';
    const prompt = 'tell me about X';
    const url = `${getApiBaseUrl()}/api/v1/compose`;
    const body = { ...baseCtx(sessionId), prompt };
    const data = await postJson(url, body, COMPOSE_TIMEOUT) as { identityContext?: string; retrievedEvidence?: string } | null;
    const additional = buildAdditionalContext(data!);
    expect(additional).toBeNull();
  });
});