import { agenticRAGProcessor } from '../agenticRAGProcessor';
import { llmService } from '../llmService';
import { AgentExecutionModel, AgenticRAGSessionModel, QualityMetricsModel } from '../../models/AgenticRAGModels';
import { config } from '../../config/env';
import { QualityMetrics } from '../../models/agenticTypes';

// Mock dependencies
jest.mock('../llmService');
jest.mock('../../models/AgenticRAGModels');
jest.mock('../../config/env');
jest.mock('../../utils/logger');

const mockLLMService = llmService as jest.Mocked<typeof llmService>;
const mockAgentExecutionModel = AgentExecutionModel as jest.Mocked<typeof AgentExecutionModel>;
const mockAgenticRAGSessionModel = AgenticRAGSessionModel as jest.Mocked<typeof AgenticRAGSessionModel>;
const mockQualityMetricsModel = QualityMetricsModel as jest.Mocked<typeof QualityMetricsModel>;

describe('AgenticRAGProcessor', () => {
  let processor: any;
  
  beforeEach(() => {
    jest.clearAllMocks();
    
    // Mock config
    Object.assign(config, {
      agenticRag: {
        enabled: true,
        maxAgents: 6,
        parallelProcessing: true,
        validationStrict: true,
        retryAttempts: 3,
        timeoutPerAgent: 60000,
      },
      agentSpecific: {
        documentUnderstandingEnabled: true,
        financialAnalysisEnabled: true,
        marketAnalysisEnabled: true,
        investmentThesisEnabled: true,
        synthesisEnabled: true,
        validationEnabled: true,
      },
      llm: {
        maxTokens: 3000,
        temperature: 0.1,
      },
    });

    // Mock successful LLM responses using the public method
    mockLLMService.processCIMDocument.mockResolvedValue({
      success: true,
      jsonOutput: createMockAgentResponse('document_understanding'),
      model: 'claude-3-opus-20240229',
      cost: 0.50,
      inputTokens: 1000,
      outputTokens: 500,
    });

    // Mock database operations
    mockAgenticRAGSessionModel.create.mockResolvedValue(createMockSession());
    mockAgenticRAGSessionModel.update.mockResolvedValue(createMockSession());
    mockAgentExecutionModel.create.mockResolvedValue(createMockExecution());
    mockAgentExecutionModel.update.mockResolvedValue(createMockExecution());
    mockAgentExecutionModel.getBySessionId.mockResolvedValue([createMockExecution()]);
    mockQualityMetricsModel.create.mockResolvedValue(createMockQualityMetric());

    processor = agenticRAGProcessor;
  });

  describe('processDocument', () => {
    it('should successfully process document with all agents', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      // Mock successful agent responses for all steps
      mockLLMService.processCIMDocument
        .mockResolvedValueOnce({
          success: true,
          jsonOutput: createMockAgentResponse('document_understanding'),
          model: 'claude-3-opus-20240229',
          cost: 0.50,
          inputTokens: 1000,
          outputTokens: 500,
        })
        .mockResolvedValueOnce({
          success: true,
          jsonOutput: createMockAgentResponse('financial_analysis'),
          model: 'claude-3-opus-20240229',
          cost: 0.50,
          inputTokens: 1000,
          outputTokens: 500,
        })
        .mockResolvedValueOnce({
          success: true,
          jsonOutput: createMockAgentResponse('market_analysis'),
          model: 'claude-3-opus-20240229',
          cost: 0.50,
          inputTokens: 1000,
          outputTokens: 500,
        })
        .mockResolvedValueOnce({
          success: true,
          jsonOutput: createMockAgentResponse('investment_thesis'),
          model: 'claude-3-opus-20240229',
          cost: 0.50,
          inputTokens: 1000,
          outputTokens: 500,
        })
        .mockResolvedValueOnce({
          success: true,
          jsonOutput: createMockAgentResponse('synthesis'),
          model: 'claude-3-opus-20240229',
          cost: 0.50,
          inputTokens: 1000,
          outputTokens: 500,
        })
        .mockResolvedValueOnce({
          success: true,
          jsonOutput: createMockAgentResponse('validation'),
          model: 'claude-3-opus-20240229',
          cost: 0.50,
          inputTokens: 1000,
          outputTokens: 500,
        });

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      expect(result.success).toBe(true);
      expect(result.reasoningSteps).toBeDefined();
      expect(result.qualityMetrics).toBeDefined();
      expect(result.processingTime).toBeGreaterThan(0);
      expect(result.sessionId).toBeDefined();
      expect(result.error).toBeUndefined();

      // Verify session was created and updated
      expect(mockAgenticRAGSessionModel.create).toHaveBeenCalledWith(
        expect.objectContaining({
          documentId,
          userId,
          strategy: 'agentic_rag',
          status: 'pending',
          totalAgents: 6,
        })
      );

      // Verify all agents were executed
      expect(mockLLMService.processCIMDocument).toHaveBeenCalledTimes(6);
    });

    it('should handle agent failures gracefully', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      // Mock one agent failure
      mockLLMService.processCIMDocument
        .mockResolvedValueOnce({
          success: true,
          jsonOutput: createMockAgentResponse('document_understanding'),
          model: 'claude-3-opus-20240229',
          cost: 0.50,
          inputTokens: 1000,
          outputTokens: 500,
        })
        .mockRejectedValueOnce(new Error('Financial analysis failed'));

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      expect(result.success).toBe(false);
      expect(result.error).toContain('Financial analysis failed');
      expect(result.reasoningSteps).toBeDefined();
      expect(result.sessionId).toBeDefined();

      // Verify session was marked as failed
      expect(mockAgenticRAGSessionModel.update).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          status: 'failed',
        })
      );
    });

    it('should retry failed agents according to retry strategy', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      // Mock agent that fails twice then succeeds
      mockLLMService.processCIMDocument
        .mockRejectedValueOnce(new Error('Temporary failure'))
        .mockRejectedValueOnce(new Error('Temporary failure'))
        .mockResolvedValueOnce({
          success: true,
          jsonOutput: createMockAgentResponse('document_understanding'),
          model: 'claude-3-opus-20240229',
          cost: 0.50,
          inputTokens: 1000,
          outputTokens: 500,
        });

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      expect(mockLLMService.processCIMDocument).toHaveBeenCalledTimes(3);
      expect(result.success).toBe(true);
    });

    it('should assess quality metrics correctly', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      // Mock successful processing
      mockLLMService.processCIMDocument.mockResolvedValue({
        success: true,
        jsonOutput: createMockAgentResponse('document_understanding'),
        model: 'claude-3-opus-20240229',
        cost: 0.50,
        inputTokens: 1000,
        outputTokens: 500,
      });

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      expect(result.qualityMetrics).toBeDefined();
      expect(result.qualityMetrics.length).toBeGreaterThan(0);
      expect(result.qualityMetrics.every((m: QualityMetrics) => m.metricValue >= 0 && m.metricValue <= 1)).toBe(true);
    });

    it('should handle circuit breaker pattern', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      // Mock repeated failures to trigger circuit breaker
      mockLLMService.processCIMDocument.mockRejectedValue(new Error('Service unavailable'));

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      expect(result.success).toBe(false);
      expect(result.error).toContain('Service unavailable');
    });

    it('should track API calls and costs', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      // Mock successful processing
      mockLLMService.processCIMDocument.mockResolvedValue({
        success: true,
        jsonOutput: createMockAgentResponse('document_understanding'),
        model: 'claude-3-opus-20240229',
        cost: 0.50,
        inputTokens: 1000,
        outputTokens: 500,
      });

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      expect(result.apiCalls).toBeGreaterThan(0);
      expect(result.totalCost).toBeDefined();
    });
  });

  describe('error handling', () => {
    it('should handle database errors gracefully', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      mockAgenticRAGSessionModel.create.mockRejectedValue(new Error('Database connection failed'));

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      expect(result.success).toBe(false);
      expect(result.error).toContain('Database connection failed');
    });

    it('should handle invalid JSON responses', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      mockLLMService.processCIMDocument.mockResolvedValue({
        success: false,
        error: 'Invalid JSON response',
        model: 'claude-3-opus-20240229',
        cost: 0.50,
        inputTokens: 1000,
        outputTokens: 500,
      });

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      expect(result.success).toBe(false);
      expect(result.error).toContain('Failed to parse JSON');
    });
  });

  describe('configuration', () => {
    it('should respect agent-specific configuration', async () => {
      // Arrange
      const documentText = loadTestDocument();
      const documentId = 'test-doc-123';
      const userId = 'test-user-123';

      // Disable some agents
      (config as any).agentSpecific.financialAnalysisEnabled = false;
      (config as any).agentSpecific.marketAnalysisEnabled = false;

      mockLLMService.processCIMDocument.mockResolvedValue({
        success: true,
        jsonOutput: createMockAgentResponse('document_understanding'),
        model: 'claude-3-opus-20240229',
        cost: 0.50,
        inputTokens: 1000,
        outputTokens: 500,
      });

      // Act
      const result = await processor.processDocument(documentText, documentId, userId);

      // Assert
      // Should still work with enabled agents
      expect(result.success).toBeDefined();
    });
  });
});

// Helper functions
function createMockAgentResponse(agentName: string): any {
  const responses: Record<string, any> = {
    document_understanding: {
      companyOverview: {
        name: 'Test Company',
        industry: 'Technology',
        location: 'San Francisco, CA',
        founded: '2010',
        employees: '500'
      },
      documentStructure: {
        sections: ['Executive Summary', 'Financial Analysis', 'Market Analysis'],
        pageCount: 50,
        keyTopics: ['Financial Performance', 'Market Position', 'Growth Strategy']
      },
      financialHighlights: {
        revenue: '$100M',
        ebitda: '$20M',
        growth: '15%',
        margins: '20%'
      }
    },
    financial_analysis: {
      historicalPerformance: {
        revenue: ['$80M', '$90M', '$100M'],
        ebitda: ['$15M', '$18M', '$20M'],
        margins: ['18%', '20%', '20%']
      },
      qualityOfEarnings: 'High',
      workingCapital: 'Positive',
      cashFlow: 'Strong'
    },
    market_analysis: {
      marketSize: '$10B',
      growthRate: '8%',
      competitors: ['Competitor A', 'Competitor B'],
      barriersToEntry: 'High',
      competitiveAdvantages: ['Technology', 'Brand', 'Scale']
    },
    investment_thesis: {
      keyAttractions: ['Strong growth', 'Market leadership', 'Technology advantage'],
      potentialRisks: ['Market competition', 'Regulatory changes'],
      valueCreation: ['Operational improvements', 'Market expansion'],
      recommendation: 'Proceed with diligence'
    },
    synthesis: {
      dealOverview: {
        targetCompanyName: 'Test Company',
        industrySector: 'Technology',
        geography: 'San Francisco, CA'
      },
      financialSummary: {
        financials: {
          ltm: {
            revenue: '$100M',
            ebitda: '$20M'
          }
        }
      },
      preliminaryInvestmentThesis: {
        keyAttractions: ['Strong growth', 'Market leadership'],
        potentialRisks: ['Market competition']
      }
    },
    validation: {
      isValid: true,
      issues: [],
      completeness: '95%',
      quality: 'high'
    }
  };

  return responses[agentName] || {};
}

function createMockSession(): any {
  return {
    id: 'session-123',
    documentId: 'doc-123',
    userId: 'user-123',
    strategy: 'agentic_rag',
    status: 'completed',
    totalAgents: 6,
    completedAgents: 6,
    failedAgents: 0,
    overallValidationScore: 0.9,
    processingTimeMs: 120000,
    apiCallsCount: 6,
    totalCost: 2.50,
    reasoningSteps: [],
    finalResult: {},
    createdAt: new Date(),
    completedAt: new Date()
  };
}

function createMockExecution(): any {
  return {
    id: 'execution-123',
    documentId: 'doc-123',
    sessionId: 'session-123',
    agentName: 'document_understanding',
    stepNumber: 1,
    status: 'completed',
    inputData: {},
    outputData: createMockAgentResponse('document_understanding'),
    validationResult: true,
    processingTimeMs: 20000,
    errorMessage: null,
    retryCount: 0,
    createdAt: new Date(),
    updatedAt: new Date()
  };
}

function createMockQualityMetric(): any {
  return {
    id: 'metric-123',
    documentId: 'doc-123',
    sessionId: 'session-123',
    metricType: 'completeness',
    metricValue: 0.9,
    metricDetails: {
      requiredSections: 7,
      presentSections: 6,
      missingSections: ['managementTeamOverview']
    },
    createdAt: new Date()
  };
}

function loadTestDocument(): string {
  // Mock document content for testing
  return `
  CONFIDENTIAL INVESTMENT MEMORANDUM
  
  Test Company, Inc.
  
  Executive Summary
  Test Company is a leading technology company with strong financial performance and market position.
  
  Financial Performance
  - Revenue: $100M (2023)
  - EBITDA: $20M (2023)
  - Growth Rate: 15% annually
  
  Market Position
  - Market Size: $10B
  - Market Share: 5%
  - Competitive Advantages: Technology, Brand, Scale
  
  Management Team
  - CEO: John Smith (10+ years experience)
  - CFO: Jane Doe (15+ years experience)
  
  Investment Opportunity
  - Strong growth potential
  - Market leadership position
  - Technology advantage
  - Experienced management team
  
  Risks and Considerations
  - Market competition
  - Regulatory changes
  - Technology disruption
  
  This memorandum contains confidential information and is for internal use only.
  `;
} 