import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { mkdtemp, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';

import { executeGetCodebaseOverview } from '../tools/codecontext/get_codebase_overview.js';
import { executeGetFileAnalysis } from '../tools/codecontext/get_file_analysis.js';
import { executeGetSymbolInfo } from '../tools/codecontext/get_symbol_info.js';
import { executeSearchSymbols } from '../tools/codecontext/search_symbols.js';
import { executeGetDependencies } from '../tools/codecontext/get_dependencies.js';
import { executeWatchChanges } from '../tools/codecontext/watch_changes.js';
import { executeGetSemanticNeighborhoods } from '../tools/codecontext/get_semantic_neighborhoods.js';
import { executeGetFrameworkAnalysis } from '../tools/codecontext/get_framework_analysis.js';

// ---- fixtures ---------------------------------------------------------------

let projectDir: string;

beforeEach(async () => {
  projectDir = await mkdtemp(join(tmpdir(), 'codecontext-tools-test-'));
});

afterEach(async () => {
  await rm(projectDir, { recursive: true, force: true });
  vi.restoreAllMocks();
});

function mockJSONResponse(body: unknown, status = 200): Response {
  return new Response(JSON.stringify(body), {
    status,
    headers: { 'content-type': 'application/json' },
  });
}

// Stub fetcher that records every call and returns a canned successful body.
// Each test inspects fetcher.mock.calls[0] to assert URL + body shape.
function makeStub() {
  return vi.fn().mockResolvedValue(
    mockJSONResponse({ result: 'wrapped ok', error: null }),
  );
}

function parsePOST(fetcher: ReturnType<typeof makeStub>): {
  url: string;
  body: Record<string, unknown>;
} {
  expect(fetcher).toHaveBeenCalledTimes(1);
  const [url, init] = fetcher.mock.calls[0]! as [string, { body: string }];
  return { url, body: JSON.parse(init.body) };
}

// ---- per-wrapper smoke tests -----------------------------------------------

describe('codecontext wrappers — toolName + args forwarding', () => {
  it('get_codebase_overview posts to /v1/get_codebase_overview with include_stats default true', async () => {
    const fetcher = makeStub();
    await executeGetCodebaseOverview({}, projectDir, fetcher as unknown as typeof fetch);
    const { url, body } = parsePOST(fetcher);
    expect(url).toMatch(/\/v1\/get_codebase_overview$/);
    expect(body).toMatchObject({ include_stats: true, target_dir: projectDir });
  });

  it('get_file_analysis forwards file_path', async () => {
    const fetcher = makeStub();
    await executeGetFileAnalysis(
      { file_path: 'apps/server/src/index.ts' },
      projectDir,
      fetcher as unknown as typeof fetch,
    );
    const { url, body } = parsePOST(fetcher);
    expect(url).toMatch(/\/v1\/get_file_analysis$/);
    expect(body).toMatchObject({
      file_path: join(projectDir, 'apps/server/src/index.ts'),
      target_dir: projectDir,
    });
  });

  it('get_symbol_info forwards symbol_name and omits optional fields when unset', async () => {
    const fetcher = makeStub();
    await executeGetSymbolInfo(
      { symbol_name: 'buildSystemPrompt' },
      projectDir,
      fetcher as unknown as typeof fetch,
    );
    const { url, body } = parsePOST(fetcher);
    expect(url).toMatch(/\/v1\/get_symbol_info$/);
    expect(body).toMatchObject({ symbol_name: 'buildSystemPrompt', target_dir: projectDir });
    expect(body).not.toHaveProperty('file_path');
    expect(body).not.toHaveProperty('framework_type');
  });

  it('search_symbols defaults limit to 20 and forwards filters when set', async () => {
    const fetcher = makeStub();
    await executeSearchSymbols(
      { query: 'User', symbol_type: 'class' },
      projectDir,
      fetcher as unknown as typeof fetch,
    );
    const { url, body } = parsePOST(fetcher);
    expect(url).toMatch(/\/v1\/search_symbols$/);
    expect(body).toMatchObject({
      query: 'User',
      symbol_type: 'class',
      limit: 20,
      target_dir: projectDir,
    });
  });

  it('get_dependencies defaults direction to "both"', async () => {
    const fetcher = makeStub();
    await executeGetDependencies({}, projectDir, fetcher as unknown as typeof fetch);
    const { url, body } = parsePOST(fetcher);
    expect(url).toMatch(/\/v1\/get_dependencies$/);
    expect(body).toMatchObject({ direction: 'both', target_dir: projectDir });
    expect(body).not.toHaveProperty('file_path');
  });

  it('watch_changes forwards enable=false', async () => {
    const fetcher = makeStub();
    await executeWatchChanges(
      { enable: false },
      projectDir,
      fetcher as unknown as typeof fetch,
    );
    const { url, body } = parsePOST(fetcher);
    expect(url).toMatch(/\/v1\/watch_changes$/);
    expect(body).toMatchObject({ enable: false, target_dir: projectDir });
  });

  it('get_semantic_neighborhoods defaults max_results to 10', async () => {
    const fetcher = makeStub();
    await executeGetSemanticNeighborhoods(
      {},
      projectDir,
      fetcher as unknown as typeof fetch,
    );
    const { url, body } = parsePOST(fetcher);
    expect(url).toMatch(/\/v1\/get_semantic_neighborhoods$/);
    expect(body).toMatchObject({ max_results: 10, target_dir: projectDir });
  });

  it('get_framework_analysis sends only target_dir when no args are provided', async () => {
    const fetcher = makeStub();
    await executeGetFrameworkAnalysis(
      {},
      projectDir,
      fetcher as unknown as typeof fetch,
    );
    const { url, body } = parsePOST(fetcher);
    expect(url).toMatch(/\/v1\/get_framework_analysis$/);
    expect(body).toMatchObject({ target_dir: projectDir });
    expect(body).not.toHaveProperty('framework');
    expect(body).not.toHaveProperty('include_stats');
  });
});
