import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setupDocumentationTools } from '../tools.js';
import { SqliteManager } from '../../../storage/sqlite-manager.js';

// Mock SqliteManager
vi.mock('../../../storage/sqlite-manager.js', () => ({
  SqliteManager: {
    getInstance: vi.fn()
  }
}));

describe('Documentation Module', () => {
  let tools: any;
  let mockDb: vi.Mocked<SqliteManager>;

  beforeEach(async () => {
    vi.clearAllMocks();
    
    // Mock database operations
    mockDb = {
      run: vi.fn().mockResolvedValue({ success: true, changes: 1 }),
      get: vi.fn().mockResolvedValue({ success: true, data: null }),
      query: vi.fn().mockResolvedValue({ success: true, data: [] }),
      transaction: vi.fn().mockImplementation(async (callback) => {
        return await callback(mockDb);
      })
    } as any;
    
    vi.mocked(SqliteManager.getInstance).mockReturnValue(mockDb);
    
    // Setup tools
    const toolRegistration = await setupDocumentationTools();
    tools = {};
    for (const tool of toolRegistration.tools) {
      tools[tool.name] = tool;
    }
  });

  const createContext = (toolName: string) => ({
    toolName,
    requestId: 'test-req-1',
    projectId: 'test-project',
    userId: 'test-user',
    timestamp: Date.now(),
    db: mockDb
  });

  describe('generate_readme', () => {
    it('should generate README document in database', async () => {
      const input = {
        projectName: 'Test Project',
        description: 'A test project',
        features: ['Feature 1', 'Feature 2'],
        techStack: ['TypeScript', 'Node.js'],
      };

      const result = await tools.generate_readme.execute(input, createContext('generate_readme'));

      expect(result.success).toBe(true);
      expect(result.data.message).toContain('README.md created for project "Test Project"');
      expect(result.data.document.title).toBe('README.md');
      expect(result.data.document.type).toBe('readme');
      expect(result.data.document.created).toBe(true);
      expect(mockDb.run).toHaveBeenCalled();
    });

    it('should update existing README document', async () => {
      // Mock existing README
      mockDb.get.mockResolvedValueOnce({
        success: true,
        data: {
          id: 'existing-readme-id',
          title: 'README.md',
          type: 'readme'
        }
      });

      const input = {
        projectName: 'Updated Project',
        description: 'An updated test project',
        repository: 'frontend-repo',
      };

      const result = await tools.generate_readme.execute(input, createContext('generate_readme'));

      expect(result.success).toBe(true);
      expect(result.data.message).toContain('README.md updated for project "Updated Project"');
      expect(result.data.document.updated).toBe(true);
      expect(mockDb.run).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE documents'),
        expect.any(Array)
      );
    });

    it('should handle database errors gracefully', async () => {
      mockDb.run.mockResolvedValueOnce({ success: false, error: 'Database error' });

      const input = {
        projectName: 'Test Project',
        description: 'A test project',
      };

      const result = await tools.generate_readme.execute(input, createContext('generate_readme'));

      expect(result.success).toBe(false);
      expect(result.error).toBeDefined();
      expect(result.error?.code).toBe('DATABASE_ERROR');
    });
  });

  describe('generate_claude_config', () => {
    it('should generate CLAUDE.md configuration', async () => {
      const input = {
        tddMode: 'strict' as const,
        testCommand: 'npm test',
        lintCommand: 'npm run lint',
        customInstructions: ['Use TypeScript', 'Follow TDD'],
      };

      const result = await tools.generate_claude_config.execute(input, createContext('generate_claude_config'));

      expect(result.success).toBe(true);
      expect(result.data.message).toBe('CLAUDE.md configuration created');
      expect(result.data.config.tddMode).toBe('strict');
      expect(result.data.document.created).toBe(true);
      expect(mockDb.run).toHaveBeenCalled();
    });

    it('should update existing CLAUDE.md configuration', async () => {
      // Mock existing CLAUDE.md
      mockDb.get.mockResolvedValueOnce({
        success: true,
        data: {
          id: 'existing-claude-id',
          title: 'CLAUDE.md',
          type: 'claude-config'
        }
      });

      const input = {
        tddMode: 'moderate' as const,
        buildCommand: 'npm run build:prod',
      };

      const result = await tools.generate_claude_config.execute(input, createContext('generate_claude_config'));

      expect(result.success).toBe(true);
      expect(result.data.message).toBe('CLAUDE.md configuration updated');
      expect(result.data.document.updated).toBe(true);
      expect(mockDb.run).toHaveBeenCalledWith(
        expect.stringContaining('UPDATE documents'),
        expect.any(Array)
      );
    });
  });

  describe('create_documentation', () => {
    it('should create API documentation', async () => {
      const input = {
        type: 'api' as const,
        title: 'REST API',
        content: 'API endpoints documentation...',
      };

      const result = await tools.create_documentation.execute(input, createContext('create_documentation'));

      expect(result.success).toBe(true);
      expect(result.data.message).toBe('Documentation created: API.md');
      expect(result.data.document.type).toBe('api');
      expect(result.data.document.path).toBe('docs/API.md');
      expect(mockDb.run).toHaveBeenCalled();
    });

    it('should create custom documentation with repository context', async () => {
      const input = {
        type: 'custom' as const,
        title: 'Mobile Setup Guide',
        content: 'How to set up the mobile development environment...',
        repository: 'mobile-repo',
      };

      const result = await tools.create_documentation.execute(input, createContext('create_documentation'));

      expect(result.success).toBe(true);
      expect(result.data.message).toBe('Documentation created: mobile-setup-guide.md');
      expect(result.data.document.path).toBe('mobile-repo/docs/mobile-setup-guide.md');
      expect(mockDb.run).toHaveBeenCalled();
    });
  });

  describe('list_documents', () => {
    it('should list documentation files', async () => {
      mockDb.query.mockResolvedValueOnce({
        success: true,
        data: [
          {
            id: 'doc-1',
            title: 'README.md',
            type: 'readme',
            path: 'README.md',
            status: 'published',
            author: 'test-user',
            version: 1,
            tags: '["readme", "documentation"]',
            created_at: Date.now(),
            updated_at: Date.now(),
            content: '# Test Project\n\nThis is a test project...'
          },
          {
            id: 'doc-2',
            title: 'API Documentation',
            type: 'api',
            path: 'docs/API.md',
            status: 'draft',
            author: 'test-user',
            version: 2,
            tags: '["api", "documentation"]',
            created_at: Date.now(),
            updated_at: Date.now(),
            content: '# API\n\nAPI documentation...'
          }
        ]
      });

      const result = await tools.list_documents.execute({}, createContext('list_documents'));

      expect(result.success).toBe(true);
      expect(result.data.documents).toHaveLength(2);
      expect(result.data.documents[0].title).toBe('README.md');
      expect(result.data.documents[1].title).toBe('API Documentation');
      expect(mockDb.query).toHaveBeenCalled();
    });

    it('should filter documents by type', async () => {
      mockDb.query.mockResolvedValueOnce({
        success: true,
        data: []
      });

      const result = await tools.list_documents.execute({ type: 'api' }, createContext('list_documents'));

      expect(result.success).toBe(true);
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining('AND type = ?'),
        expect.arrayContaining(['api'])
      );
    });
  });

  describe('update_document', () => {
    it('should update an existing document', async () => {
      // Mock document check
      mockDb.get.mockResolvedValueOnce({
        success: true,
        data: {
          id: 'doc-1',
          title: 'Old Title',
          type: 'api'
        }
      });

      const input = {
        documentId: 'doc-1',
        title: 'Updated API Documentation',
        status: 'published' as const,
        tags: ['api', 'v2', 'rest']
      };

      const result = await tools.update_document.execute(input, createContext('update_document'));

      expect(result.success).toBe(true);
      expect(result.data.message).toBe('Document "Updated API Documentation" updated successfully');
      expect(result.data.changes).toContain('title');
      expect(result.data.changes).toContain('status');
      expect(result.data.changes).toContain('tags');
      expect(mockDb.run).toHaveBeenCalled();
    });

    it('should handle document not found', async () => {
      mockDb.get.mockResolvedValueOnce({
        success: true,
        data: null
      });

      const result = await tools.update_document.execute({
        documentId: 'non-existent',
        title: 'New Title'
      }, createContext('update_document'));

      expect(result.success).toBe(false);
      expect(result.error?.code).toBe('RESOURCE_NOT_FOUND');
    });
  });

  describe('search_documents', () => {
    it('should search documents by content or title', async () => {
      mockDb.query.mockResolvedValueOnce({
        success: true,
        data: [
          {
            id: 'doc-1',
            title: 'API Reference',
            type: 'api',
            path: 'docs/api.md',
            content: 'This is the API reference guide for our REST endpoints...',
            updated_at: Date.now()
          },
          {
            id: 'doc-2',
            title: 'Getting Started',
            type: 'guide',
            path: 'docs/getting-started.md',
            content: 'Welcome to our API. This guide will help you...',
            updated_at: Date.now()
          }
        ]
      });

      const result = await tools.search_documents.execute({
        query: 'API'
      }, createContext('search_documents'));

      expect(result.success).toBe(true);
      expect(result.data.results).toHaveLength(2);
      expect(result.data.results[0].title).toBe('API Reference');
      expect(result.data.results[0].score).toBe(1); // Title match
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining('LIKE ?'),
        expect.arrayContaining(['%API%'])
      );
    });

    it('should filter search by document type', async () => {
      mockDb.query.mockResolvedValueOnce({
        success: true,
        data: []
      });

      const result = await tools.search_documents.execute({
        query: 'test',
        type: 'api'
      }, createContext('search_documents'));

      expect(result.success).toBe(true);
      expect(mockDb.query).toHaveBeenCalledWith(
        expect.stringContaining('AND type = ?'),
        expect.arrayContaining(['api'])
      );
    });
  });

  describe('tool registration', () => {
    it('should register all expected tools', () => {
      const expectedTools = [
        'generate_readme',
        'generate_claude_config',
        'create_documentation',
        'list_documents',
        'update_document',
        'search_documents'
      ];

      for (const toolName of expectedTools) {
        expect(tools[toolName]).toBeDefined();
        expect(typeof tools[toolName].execute).toBe('function');
      }
    });
  });

  describe('error handling', () => {
    it('should handle database errors gracefully', async () => {
      mockDb.run.mockResolvedValueOnce({ success: false, error: 'Database error' });

      const input = {
        projectName: 'Test Project',
        description: 'A test project',
      };

      const result = await tools.generate_readme.execute(input, createContext('generate_readme'));

      expect(result.success).toBe(false);
      expect(result.error).toBeDefined();
    });
  });
});