import { describe, it, expect, vi, beforeEach } from 'vitest';
import { TOOL_DEFS, executeTool } from './knowledge.js';
import type { ToolContext } from './core.js';

// mock fetch
const mockFetch = vi.fn();
global.fetch = mockFetch as any;

function makeCtx(workspacePath: string): ToolContext {
  return {
    workspacePath,
    editAllowed: false,
    toolsConfig: {
      knowledgeServiceUrl: 'http://dks:8100',
      knowledgeNamespaces: {
        'test-ns': { apiKey: 'sk-test-key' },
      },
    },
  };
}

describe('knowledge tools', () => {
  beforeEach(() => {
    mockFetch.mockReset();
  });

  describe('TOOL_DEFS', () => {
    it('exports all knowledge tool definitions', () => {
      expect(TOOL_DEFS).toHaveProperty('IngestDocument');
      expect(TOOL_DEFS).toHaveProperty('IngestStatus');
      expect(TOOL_DEFS).toHaveProperty('SearchKnowledge');
      expect(TOOL_DEFS).toHaveProperty('ListNamespaces');
      expect(TOOL_DEFS).toHaveProperty('ListDocuments');
    });
  });

  describe('SearchKnowledge', () => {
    it('calls DKS search API and formats response with local image paths', async () => {
      // First call: search API, second call: image download
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            sections: [
              { title: 'Section 1', content: 'テスト内容', pages: [1, 2], doc_id: 'doc-1', doc_name: 'manual.pdf' },
            ],
            page_image_urls: ['/namespaces/test-ns/documents/doc-1/pages/page_001.png'],
            system_status: { ingest_active: false, ingest_jobs: 0, message: '' },
          }),
        })
        .mockResolvedValueOnce({
          ok: true,
          arrayBuffer: async () => new ArrayBuffer(8),
        });

      const result = await executeTool('SearchKnowledge', {
        namespace: 'test-ns',
        query: 'セットアップ方法',
      }, makeCtx('/tmp/knowledge-test'));

      expect(result).not.toBeNull();
      expect(result!.isError).toBe(false);
      expect(result!.output).toContain('Section 1');
      expect(result!.output).toContain('input/knowledge/test-ns/page_001.png');
      expect(result!.output).toContain('ReadImage');
    });

    it('reports image download errors without failing the search', async () => {
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({
            sections: [
              { title: 'Section 1', content: 'テスト内容', pages: [1], doc_id: 'doc-1', doc_name: 'manual.pdf' },
            ],
            page_image_urls: ['/namespaces/test-ns/documents/doc-1/pages/page_001.png'],
          }),
        })
        .mockResolvedValueOnce({ ok: false, status: 404 });

      const result = await executeTool('SearchKnowledge', {
        namespace: 'test-ns',
        query: 'test',
      }, makeCtx('/tmp/knowledge-test'));

      expect(result!.isError).toBe(false);
      expect(result!.output).toContain('Section 1');
      expect(result!.output).toContain('画像ダウンロードエラー');
    });

    it('returns error for unknown namespace', async () => {
      const result = await executeTool('SearchKnowledge', {
        namespace: 'unknown-ns',
        query: 'test',
      }, makeCtx('/tmp/ws'));

      expect(result!.isError).toBe(true);
      expect(result!.output).toContain('not configured');
    });
  });

  describe('IngestStatus', () => {
    it('returns job status', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          job_id: 'ingest-123',
          status: 'processing',
          document_name: 'manual.pdf',
          progress: { total_pages: 45, text_extracted: 45, vlm_completed: 20, tree_built: false },
          attempt: 1,
          error: null,
          completed_at: null,
        }),
      });

      const result = await executeTool('IngestStatus', {
        namespace: 'test-ns',
        job_id: 'ingest-123',
      }, makeCtx('/tmp/ws'));

      expect(result!.isError).toBe(false);
      expect(result!.output).toContain('ingest-123');
      expect(result!.output).toContain('20/45');
    });
  });

  describe('ListDocuments', () => {
    it('returns document list', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        json: async () => ({
          documents: [
            { id: 'doc-1', name: 'manual.pdf', namespace: 'test-ns', page_count: 10, created_at: '2026-01-01' },
          ],
        }),
      });

      const result = await executeTool('ListDocuments', {
        namespace: 'test-ns',
      }, makeCtx('/tmp/ws'));

      expect(result!.isError).toBe(false);
      expect(result!.output).toContain('manual.pdf');
    });
  });

  describe('ListNamespaces', () => {
    it('returns configured namespaces', async () => {
      const result = await executeTool('ListNamespaces', {}, makeCtx('/tmp/ws'));

      expect(result).not.toBeNull();
      expect(result!.isError).toBe(false);
      expect(result!.output).toContain('test-ns');
    });
  });

  it('returns null for unknown tool', async () => {
    const result = await executeTool('UnknownTool', {}, makeCtx('/tmp/ws'));
    expect(result).toBeNull();
  });

  describe('with missing toolsConfig', () => {
    it('returns error when knowledge config is not set', async () => {
      const ctx: ToolContext = { workspacePath: '/tmp/ws', editAllowed: false };
      const result = await executeTool('SearchKnowledge', {
        namespace: 'test-ns',
        query: 'test',
      }, ctx);

      expect(result!.isError).toBe(true);
      expect(result!.output).toContain('not configured');
    });
  });
});
