import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setupADRManagementModule } from '../index.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { ADRManager } from '../adr-manager.js';
import { ADRWizard } from '../adr-wizard.js';

// Mock dependencies
vi.mock('../adr-manager.js', () => ({
  ADRManager: vi.fn().mockImplementation(() => ({
    initialize: vi.fn().mockResolvedValue(undefined),
    createADR: vi.fn(),
    updateADR: vi.fn(),
    linkADRs: vi.fn(),
    getADR: vi.fn(),
    listADRs: vi.fn(),
    searchADRs: vi.fn(),
    deleteADR: vi.fn(),
    getMetrics: vi.fn(),
    validateReferences: vi.fn(),
    generateDecisionLog: vi.fn()
  }))
}));

vi.mock('../adr-wizard.js', () => ({
  ADRWizard: vi.fn().mockImplementation(() => ({
    startInteractiveADR: vi.fn(),
    continueInteractiveADR: vi.fn()
  }))
}));

describe('ADR Management Module', () => {
  let server: Server;
  let mockManager: any;
  let mockWizard: any;
  let toolHandler: any;

  const testADR = {
    id: 'ADR-0001',
    title: 'Use TypeScript',
    status: 'accepted',
    date: new Date('2024-01-15'),
    deciders: ['John Doe'],
    template: 'nygard',
    context: 'Need type safety',
    decision: 'Use TypeScript',
    consequences: 'Learning curve',
    createdAt: new Date('2024-01-15'),
    updatedAt: new Date('2024-01-15'),
    statusHistory: []
  };

  beforeEach(async () => {
    server = {
      setRequestHandler: vi.fn()
    } as any;

    // Reset mocks
    vi.clearAllMocks();

    // Setup module
    const result = await setupADRManagementModule(server);

    // Get the tool handler
    toolHandler = server.setRequestHandler.mock.calls.find(
      call => call[0] === CallToolRequestSchema
    )?.[1];

    // Get mock instances
    const managerInstance = ADRManager as any;
    mockManager = managerInstance.mock.results[0].value;
    
    const wizardInstance = ADRWizard as any;
    mockWizard = wizardInstance.mock.results[0].value;
  });

  describe('Module Setup', () => {
    it('should register tool handler', async () => {
      expect(server.setRequestHandler).toHaveBeenCalledWith(
        CallToolRequestSchema,
        expect.any(Function)
      );
    });

    it('should return tool definitions', async () => {
      const result = await setupADRManagementModule(server);

      expect(result.tools).toBeDefined();
      expect(result.tools.length).toBeGreaterThan(0);
      
      const toolNames = result.tools.map(t => t.name);
      expect(toolNames).toContain('create_adr');
      expect(toolNames).toContain('create_adr_interactive');
      expect(toolNames).toContain('update_adr');
      expect(toolNames).toContain('link_adrs');
      expect(toolNames).toContain('get_adr');
      expect(toolNames).toContain('list_adrs');
      expect(toolNames).toContain('search_adrs');
      expect(toolNames).toContain('delete_adr');
      expect(toolNames).toContain('adr_metrics');
      expect(toolNames).toContain('validate_adr_references');
      expect(toolNames).toContain('generate_adr_log');
    });
  });

  describe('create_adr tool', () => {
    it('should create ADR with required fields', async () => {
      mockManager.createADR.mockResolvedValueOnce(testADR);

      const result = await toolHandler({
        params: {
          name: 'create_adr',
          arguments: {
            title: 'Use TypeScript',
            template: 'nygard',
            deciders: ['John Doe'],
            context: 'Need type safety',
            decision: 'Use TypeScript',
            consequences: 'Learning curve'
          }
        }
      });

      expect(mockManager.createADR).toHaveBeenCalledWith({
        title: 'Use TypeScript',
        template: 'nygard',
        deciders: ['John Doe'],
        context: 'Need type safety',
        decision: 'Use TypeScript',
        consequences: 'Learning curve'
      });

      expect(result.content[0].text).toContain('✅ ADR created successfully');
      expect(result.content[0].text).toContain('ADR-0001');
      expect(result.structured.adr).toEqual(testADR);
    });

    it('should create MADR with additional fields', async () => {
      const madrADR = {
        ...testADR,
        template: 'madr',
        decisionDrivers: ['Performance', 'Cost'],
        consideredOptions: [{
          title: 'Option A',
          description: 'Description',
          pros: ['Pro 1'],
          cons: ['Con 1']
        }]
      };

      mockManager.createADR.mockResolvedValueOnce(madrADR);

      const result = await toolHandler({
        params: {
          name: 'create_adr',
          arguments: {
            title: 'Choose Database',
            template: 'madr',
            deciders: ['Team'],
            context: 'Need database',
            decision: 'Use PostgreSQL',
            consequences: 'Learn PostgreSQL',
            decisionDrivers: ['Performance', 'Cost'],
            consideredOptions: [{
              title: 'Option A',
              description: 'Description',
              pros: ['Pro 1'],
              cons: ['Con 1']
            }]
          }
        }
      });

      expect(result.structured.adr.decisionDrivers).toEqual(['Performance', 'Cost']);
    });

    it('should handle errors', async () => {
      mockManager.createADR.mockRejectedValueOnce(new Error('Creation failed'));

      await expect(toolHandler({
        params: {
          name: 'create_adr',
          arguments: {
            title: 'Test',
            template: 'nygard',
            deciders: ['John'],
            context: 'Context',
            decision: 'Decision',
            consequences: 'Consequences'
          }
        }
      })).rejects.toThrow('Creation failed');
    });
  });

  describe('create_adr_interactive tool', () => {
    it('should start interactive ADR creation', async () => {
      mockWizard.startInteractiveADR.mockResolvedValueOnce({
        content: [{ type: 'text', text: 'ADR Creation Wizard started' }]
      });

      const request = {
        params: { name: 'create_adr_interactive', arguments: {} },
        sessionId: 'test-session'
      };

      const result = await toolHandler(request);

      expect(mockWizard.startInteractiveADR).toHaveBeenCalledWith(
        server,
        request,
        expect.any(Function)
      );
    });

    it('should continue interactive session', async () => {
      mockWizard.continueInteractiveADR.mockResolvedValueOnce({
        content: [{ type: 'text', text: 'Next step' }]
      });

      const request = {
        params: {
          name: 'create_adr_interactive',
          arguments: { userInput: 'Test Title' }
        },
        sessionId: 'test-session'
      };

      const result = await toolHandler(request);

      expect(mockWizard.continueInteractiveADR).toHaveBeenCalledWith(
        server,
        request,
        expect.any(Function),
        { userInput: 'Test Title' }
      );
    });
  });

  describe('update_adr tool', () => {
    it('should update ADR status', async () => {
      const updatedADR = {
        ...testADR,
        status: 'deprecated',
        updatedAt: new Date('2024-02-01')
      };

      mockManager.updateADR.mockResolvedValueOnce(updatedADR);

      const result = await toolHandler({
        params: {
          name: 'update_adr',
          arguments: {
            id: 'ADR-0001',
            status: 'deprecated',
            statusChangeReason: 'Better solution found',
            statusChangedBy: 'Jane Smith'
          }
        }
      });

      expect(mockManager.updateADR).toHaveBeenCalledWith('ADR-0001', {
        status: 'deprecated',
        statusChangeReason: 'Better solution found',
        statusChangedBy: 'Jane Smith'
      });

      expect(result.content[0].text).toContain('✅ ADR updated successfully');
      expect(result.structured.adr.status).toBe('deprecated');
    });

    it('should update other fields', async () => {
      mockManager.updateADR.mockResolvedValueOnce(testADR);

      await toolHandler({
        params: {
          name: 'update_adr',
          arguments: {
            id: 'ADR-0001',
            consequences: 'Updated consequences',
            tags: ['updated', 'typescript']
          }
        }
      });

      expect(mockManager.updateADR).toHaveBeenCalledWith('ADR-0001', {
        consequences: 'Updated consequences',
        tags: ['updated', 'typescript']
      });
    });
  });

  describe('link_adrs tool', () => {
    it('should link two ADRs', async () => {
      mockManager.linkADRs.mockResolvedValueOnce(undefined);

      const result = await toolHandler({
        params: {
          name: 'link_adrs',
          arguments: {
            fromId: 'ADR-0001',
            toId: 'ADR-0002',
            relationship: 'related-to'
          }
        }
      });

      expect(mockManager.linkADRs).toHaveBeenCalledWith(
        'ADR-0001',
        'ADR-0002',
        'related-to'
      );

      expect(result.content[0].text).toContain('✅ ADRs linked successfully');
    });

    it('should handle supersedes relationship', async () => {
      mockManager.linkADRs.mockResolvedValueOnce(undefined);

      await toolHandler({
        params: {
          name: 'link_adrs',
          arguments: {
            fromId: 'ADR-0002',
            toId: 'ADR-0001',
            relationship: 'supersedes'
          }
        }
      });

      expect(mockManager.linkADRs).toHaveBeenCalledWith(
        'ADR-0002',
        'ADR-0001',
        'supersedes'
      );
    });
  });

  describe('get_adr tool', () => {
    it('should get ADR by ID', async () => {
      mockManager.getADR.mockResolvedValueOnce({
        adr: testADR,
        markdown: '# ADR-0001: Use TypeScript',
        relationships: []
      });

      const result = await toolHandler({
        params: {
          name: 'get_adr',
          arguments: { id: 'ADR-0001' }
        }
      });

      expect(mockManager.getADR).toHaveBeenCalledWith('ADR-0001');
      expect(result.content[0].text).toContain('ADR-0001: Use TypeScript');
      expect(result.structured.adr).toEqual(testADR);
    });

    it('should handle ADR not found', async () => {
      mockManager.getADR.mockResolvedValueOnce(null);

      const result = await toolHandler({
        params: {
          name: 'get_adr',
          arguments: { id: 'ADR-9999' }
        }
      });

      expect(result.content[0].text).toContain('❌ ADR not found');
    });

    it('should show relationships', async () => {
      mockManager.getADR.mockResolvedValueOnce({
        adr: testADR,
        markdown: '# ADR-0001',
        relationships: [
          { type: 'related-to', from: 'ADR-0001', to: 'ADR-0002' },
          { type: 'supersedes', from: 'ADR-0001', to: 'ADR-0000' }
        ]
      });

      const result = await toolHandler({
        params: {
          name: 'get_adr',
          arguments: { id: 'ADR-0001' }
        }
      });

      expect(result.content[0].text).toContain('Related to: ADR-0002');
      expect(result.content[0].text).toContain('Supersedes: ADR-0000');
    });
  });

  describe('list_adrs tool', () => {
    it('should list all ADRs', async () => {
      mockManager.listADRs.mockResolvedValueOnce([
        testADR,
        { ...testADR, id: 'ADR-0002', title: 'Use React', status: 'proposed' }
      ]);

      const result = await toolHandler({
        params: {
          name: 'list_adrs',
          arguments: {}
        }
      });

      expect(mockManager.listADRs).toHaveBeenCalledWith({});
      expect(result.content[0].text).toContain('📋 Found 2 ADRs');
      expect(result.content[0].text).toContain('ADR-0001');
      expect(result.content[0].text).toContain('ADR-0002');
    });

    it('should filter by status', async () => {
      mockManager.listADRs.mockResolvedValueOnce([testADR]);

      const result = await toolHandler({
        params: {
          name: 'list_adrs',
          arguments: { status: 'accepted' }
        }
      });

      expect(mockManager.listADRs).toHaveBeenCalledWith({ status: 'accepted' });
      expect(result.content[0].text).toContain('📋 Found 1 ADR');
    });

    it('should handle empty results', async () => {
      mockManager.listADRs.mockResolvedValueOnce([]);

      const result = await toolHandler({
        params: {
          name: 'list_adrs',
          arguments: { status: 'rejected' }
        }
      });

      expect(result.content[0].text).toContain('No ADRs found');
    });
  });

  describe('search_adrs tool', () => {
    it('should search ADRs', async () => {
      mockManager.searchADRs.mockResolvedValueOnce([testADR]);

      const result = await toolHandler({
        params: {
          name: 'search_adrs',
          arguments: {
            query: 'typescript',
            tags: ['frontend']
          }
        }
      });

      expect(mockManager.searchADRs).toHaveBeenCalledWith({
        query: 'typescript',
        tags: ['frontend']
      });

      expect(result.content[0].text).toContain('🔍 Found 1 ADR');
    });

    it('should search by date range', async () => {
      mockManager.searchADRs.mockResolvedValueOnce([]);

      await toolHandler({
        params: {
          name: 'search_adrs',
          arguments: {
            dateRange: {
              from: '2024-01-01',
              to: '2024-12-31'
            }
          }
        }
      });

      expect(mockManager.searchADRs).toHaveBeenCalledWith({
        dateRange: {
          from: new Date('2024-01-01'),
          to: new Date('2024-12-31')
        }
      });
    });
  });

  describe('delete_adr tool', () => {
    it('should delete ADR', async () => {
      mockManager.deleteADR.mockResolvedValueOnce(undefined);

      const result = await toolHandler({
        params: {
          name: 'delete_adr',
          arguments: { id: 'ADR-0001' }
        }
      });

      expect(mockManager.deleteADR).toHaveBeenCalledWith('ADR-0001');
      expect(result.content[0].text).toContain('✅ ADR deleted successfully');
    });
  });

  describe('adr_metrics tool', () => {
    it('should get ADR metrics', async () => {
      const metrics = {
        total: 10,
        byStatus: {
          proposed: 2,
          accepted: 6,
          rejected: 1,
          deprecated: 1,
          superseded: 0
        },
        byTemplate: {
          nygard: 6,
          madr: 3,
          'y-statement': 1
        },
        averageDecisionTime: 5.5,
        mostActiveDeciders: [
          { name: 'John Doe', count: 5 },
          { name: 'Jane Smith', count: 3 }
        ],
        recentActivity: []
      };

      mockManager.getMetrics.mockResolvedValueOnce(metrics);

      const result = await toolHandler({
        params: {
          name: 'adr_metrics',
          arguments: {}
        }
      });

      expect(result.content[0].text).toContain('📊 **ADR Metrics**');
      expect(result.content[0].text).toContain('**Total ADRs**: 10');
      expect(result.content[0].text).toContain('Accepted: 6');
      expect(result.content[0].text).toContain('⏱️ **Average decision time**: 5.5 days');
      expect(result.structured.metrics).toEqual(metrics);
    });
  });

  describe('validate_adr_references tool', () => {
    it('should validate references', async () => {
      mockManager.validateReferences.mockResolvedValueOnce({
        valid: true,
        errors: []
      });

      const result = await toolHandler({
        params: {
          name: 'validate_adr_references',
          arguments: {}
        }
      });

      expect(result.content[0].text).toContain('✅ All ADR references are valid');
    });

    it('should report broken references', async () => {
      mockManager.validateReferences.mockResolvedValueOnce({
        valid: false,
        errors: [
          {
            adrId: 'ADR-0001',
            type: 'related-to',
            targetId: 'ADR-9999',
            error: 'Target ADR not found'
          }
        ]
      });

      const result = await toolHandler({
        params: {
          name: 'validate_adr_references',
          arguments: {}
        }
      });

      expect(result.content[0].text).toContain('❌ Found 1 broken reference');
      expect(result.content[0].text).toContain('ADR-0001');
      expect(result.content[0].text).toContain('ADR-9999');
    });
  });

  describe('generate_adr_log tool', () => {
    it('should generate decision log', async () => {
      mockManager.generateDecisionLog.mockResolvedValueOnce('# Architecture Decision Log\n...');

      const result = await toolHandler({
        params: {
          name: 'generate_adr_log',
          arguments: {}
        }
      });

      expect(result.content[0].text).toContain('📄 Architecture Decision Log Generated');
      expect(result.structured.markdown).toContain('# Architecture Decision Log');
    });
  });

  describe('Error Handling', () => {
    it('should handle missing arguments', async () => {
      await expect(toolHandler({
        params: {
          name: 'create_adr',
          arguments: undefined
        }
      })).rejects.toThrow('No arguments provided');
    });

    it('should handle unknown tool', async () => {
      const result = await toolHandler({
        params: {
          name: 'unknown_tool',
          arguments: {}
        }
      });

      expect(result).toBeNull();
    });
  });
});