import { logger } from '../utils/logger';
import { AgentExecutionModel, AgenticRAGSessionModel, QualityMetricsModel } from '../models/AgenticRAGModels';
import { 
  AgentExecution, 
  AgenticRAGSession, 
  QualityMetrics, 
  PerformanceReport,
  SessionMetrics,
  AgenticRAGHealthStatus
} from '../models/agenticTypes';
import db from '../config/database';

/**
 * Comprehensive database integration service for agentic RAG
 * Provides performance tracking, analytics, and enhanced session management
 */
export class AgenticRAGDatabaseService {
  
  /**
   * Create a new agentic RAG session with atomic transaction
   */
  async createSessionWithTransaction(
    documentId: string, 
    userId: string, 
    strategy: string
  ): Promise<AgenticRAGSession> {
    const client = await db.connect();
    
    try {
      await client.query('BEGIN');
      
      const session: Omit<AgenticRAGSession, 'id' | 'createdAt' | 'completedAt'> = {
        documentId,
        userId,
        strategy: strategy as 'agentic_rag' | 'chunking' | 'rag',
        status: 'pending',
        totalAgents: 6,
        completedAgents: 0,
        failedAgents: 0,
        apiCallsCount: 0,
        reasoningSteps: []
      };
      
      const createdSession = await AgenticRAGSessionModel.create(session);
      
      // Log session creation
      await this.logSessionEvent(createdSession.id, 'session_created', {
        documentId,
        userId,
        strategy,
        timestamp: new Date().toISOString()
      });
      
      await client.query('COMMIT');
      
      logger.info('Agentic RAG session created with transaction', { 
        sessionId: createdSession.id, 
        documentId, 
        strategy 
      });
      
      return createdSession;
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Failed to create session with transaction', { error, documentId, userId });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Update session with atomic transaction and performance tracking
   */
  async updateSessionWithMetrics(
    sessionId: string, 
    updates: Partial<AgenticRAGSession>,
    performanceData?: {
      processingTime?: number;
      apiCalls?: number;
      cost?: number;
    }
  ): Promise<void> {
    const client = await db.connect();
    
    try {
      await client.query('BEGIN');
      
      // Update session
      await AgenticRAGSessionModel.update(sessionId, updates);
      
      // Track performance metrics if provided
      if (performanceData) {
        await this.trackPerformanceMetrics(sessionId, performanceData);
      }
      
      // Log session update
      await this.logSessionEvent(sessionId, 'session_updated', {
        updates: Object.keys(updates),
        performanceData,
        timestamp: new Date().toISOString()
      });
      
      await client.query('COMMIT');
      
      logger.info('Session updated with metrics', { 
        sessionId, 
        updates: Object.keys(updates),
        performanceData 
      });
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Failed to update session with metrics', { error, sessionId, updates });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Create agent execution with atomic transaction
   */
  async createExecutionWithTransaction(
    sessionId: string, 
    agentName: string, 
    inputData: any
  ): Promise<AgentExecution> {
    const client = await db.connect();
    
    try {
      await client.query('BEGIN');
      
      const session = await AgenticRAGSessionModel.getById(sessionId);
      if (!session) {
        throw new Error(`Session ${sessionId} not found`);
      }

      const stepNumber = await this.getNextStepNumber(sessionId);
      
      const execution: Omit<AgentExecution, 'id' | 'createdAt' | 'updatedAt'> = {
        documentId: session.documentId,
        sessionId,
        agentName,
        stepNumber,
        status: 'pending',
        inputData,
        retryCount: 0
      };
      
      const createdExecution = await AgentExecutionModel.create(execution);
      
      // Log execution creation
      await this.logExecutionEvent(createdExecution.id, 'execution_created', {
        agentName,
        stepNumber,
        sessionId,
        timestamp: new Date().toISOString()
      });
      
      await client.query('COMMIT');
      
      logger.info('Agent execution created with transaction', { 
        executionId: createdExecution.id, 
        sessionId, 
        agentName, 
        stepNumber 
      });
      
      return createdExecution;
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Failed to create execution with transaction', { error, sessionId, agentName });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Update agent execution with atomic transaction
   */
  async updateExecutionWithTransaction(
    executionId: string, 
    updates: Partial<AgentExecution>
  ): Promise<AgentExecution> {
    const client = await db.connect();
    
    try {
      await client.query('BEGIN');
      
      const updatedExecution = await AgentExecutionModel.update(executionId, updates);
      
      // Log execution update
      await this.logExecutionEvent(executionId, 'execution_updated', {
        updates: Object.keys(updates),
        status: updates.status,
        timestamp: new Date().toISOString()
      });
      
      await client.query('COMMIT');
      
      return updatedExecution;
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Failed to update execution with transaction', { error, executionId, updates });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Save quality metrics with atomic transaction
   */
  async saveQualityMetricsWithTransaction(
    sessionId: string,
    metrics: Omit<QualityMetrics, 'id' | 'createdAt'>[]
  ): Promise<QualityMetrics[]> {
    const client = await db.connect();
    
    try {
      await client.query('BEGIN');
      
      const savedMetrics: QualityMetrics[] = [];
      
      for (const metric of metrics) {
        const savedMetric = await QualityMetricsModel.create(metric);
        savedMetrics.push(savedMetric);
      }
      
      // Log quality metrics creation
      await this.logSessionEvent(sessionId, 'quality_metrics_created', {
        metricCount: metrics.length,
        metricTypes: metrics.map(m => m.metricType),
        timestamp: new Date().toISOString()
      });
      
      await client.query('COMMIT');
      
      logger.info('Quality metrics saved with transaction', { 
        sessionId, 
        metricCount: metrics.length 
      });
      
      return savedMetrics;
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Failed to save quality metrics with transaction', { error, sessionId });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Get comprehensive session metrics
   */
  async getSessionMetrics(sessionId: string): Promise<SessionMetrics> {
    const session = await AgenticRAGSessionModel.getById(sessionId);
    if (!session) {
      throw new Error(`Session ${sessionId} not found`);
    }

    const executions = await AgentExecutionModel.getBySessionId(sessionId);
    const qualityMetrics = await QualityMetricsModel.getBySessionId(sessionId);
    
    const startTime = session.createdAt;
    const endTime = session.completedAt;
    const totalProcessingTime = endTime ? endTime.getTime() - startTime.getTime() : 0;
    
    return {
      sessionId: session.id,
      documentId: session.documentId,
      userId: session.userId,
      startTime,
      endTime: endTime || new Date(),
      totalProcessingTime,
      agentExecutions: executions,
      qualityMetrics,
      apiCalls: session.apiCallsCount,
      totalCost: session.totalCost || 0,
      success: session.status === 'completed',
      ...(session.status === 'failed' ? { error: 'Session failed' } : {})
    };
  }

  /**
   * Generate performance report for a time period
   */
  async generatePerformanceReport(
    startDate: Date,
    endDate: Date
  ): Promise<PerformanceReport> {
    const query = `
      SELECT 
        AVG(processing_time_ms) as avg_processing_time,
        PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY processing_time_ms) as p95_processing_time,
        AVG(api_calls_count) as avg_api_calls,
        AVG(total_cost) as avg_cost,
        COUNT(*) as total_sessions,
        COUNT(CASE WHEN status = 'completed' THEN 1 END) as successful_sessions
      FROM agentic_rag_sessions 
      WHERE created_at BETWEEN $1 AND $2
    `;

    const result = await db.query(query, [startDate, endDate]);
    const row = result.rows[0];

    // Get average quality score
    const qualityQuery = `
      SELECT AVG(metric_value) as avg_quality_score
      FROM processing_quality_metrics 
      WHERE created_at BETWEEN $1 AND $2
    `;
    
    const qualityResult = await db.query(qualityQuery, [startDate, endDate]);
    const avgQualityScore = qualityResult.rows[0]?.avg_quality_score || 0;

    const successRate = row.total_sessions > 0 ? row.successful_sessions / row.total_sessions : 0;

    return {
      averageProcessingTime: row.avg_processing_time || 0,
      p95ProcessingTime: row.p95_processing_time || 0,
      averageApiCalls: row.avg_api_calls || 0,
      averageCost: row.avg_cost || 0,
      successRate,
      averageQualityScore: parseFloat(avgQualityScore) || 0
    };
  }

  /**
   * Get agentic RAG health status
   */
  async getHealthStatus(): Promise<AgenticRAGHealthStatus> {
    // Get recent sessions (last 24 hours)
    const recentSessions = await this.getRecentSessions(24);
    
    // Calculate overall metrics
    const totalSessions = recentSessions.length;
    const successfulSessions = recentSessions.filter(s => s.status === 'completed').length;
    const successRate = totalSessions > 0 ? successfulSessions / totalSessions : 1;
    
        const avgProcessingTime = recentSessions.length > 0
      ? recentSessions.reduce((sum: number, s: any) => sum + (s.processingTimeMs || 0), 0) / recentSessions.length
      : 0;
    
    const errorRate = totalSessions > 0 ? (totalSessions - successfulSessions) / totalSessions : 0;
    
    // Get agent-specific metrics
    const agentMetrics = await this.getAgentMetrics(24);
    
    // Determine overall health status
    let overallStatus: 'healthy' | 'degraded' | 'unhealthy' = 'healthy';
    if (successRate < 0.8 || errorRate > 0.2) {
      overallStatus = 'unhealthy';
    } else if (successRate < 0.95 || errorRate > 0.05) {
      overallStatus = 'degraded';
    }
    
    return {
      status: overallStatus,
      agents: agentMetrics,
      overall: {
        successRate,
        averageProcessingTime: avgProcessingTime,
        activeSessions: recentSessions.filter(s => s.status === 'processing').length,
        errorRate
      },
      timestamp: new Date()
    };
  }

  /**
   * Get recent sessions for a time period
   */
  async getRecentSessions(hours: number): Promise<AgenticRAGSession[]> {
    const query = `
      SELECT * FROM agentic_rag_sessions 
      WHERE created_at >= NOW() - INTERVAL '${hours} hours'
      ORDER BY created_at DESC
    `;
    
    const result = await db.query(query);
    return result.rows.map((row: any) => AgenticRAGSessionModel['mapRowToSession'](row));
  }

  /**
   * Get agent-specific metrics
   */
  async getAgentMetrics(hours: number): Promise<AgenticRAGHealthStatus['agents']> {
    const query = `
      SELECT 
        agent_name,
        COUNT(*) as total_executions,
        COUNT(CASE WHEN status = 'completed' THEN 1 END) as successful_executions,
        AVG(processing_time_ms) as avg_processing_time,
        MAX(created_at) as last_execution_time
      FROM agent_executions 
      WHERE created_at >= NOW() - INTERVAL '${hours} hours'
      GROUP BY agent_name
    `;
    
    const result = await db.query(query);
    const agentMetrics: AgenticRAGHealthStatus['agents'] = {};
    
    for (const row of result.rows) {
      const successRate = row.total_executions > 0 ? row.successful_executions / row.total_executions : 1;
      
      let status: 'healthy' | 'degraded' | 'unhealthy' = 'healthy';
      if (successRate < 0.8) {
        status = 'unhealthy';
      } else if (successRate < 0.95) {
        status = 'degraded';
      }
      
      agentMetrics[row.agent_name] = {
        status,
        ...(row.last_execution_time ? { lastExecutionTime: new Date(row.last_execution_time).getTime() } : {}),
        successRate,
        averageProcessingTime: row.avg_processing_time || 0
      };
    }
    
    return agentMetrics;
  }

  /**
   * Track performance metrics
   */
  private async trackPerformanceMetrics(
    sessionId: string, 
    data: { processingTime?: number; apiCalls?: number; cost?: number }
  ): Promise<void> {
    const query = `
      INSERT INTO performance_metrics (session_id, metric_type, metric_value, created_at)
      VALUES ($1, $2, $3, NOW())
    `;
    
    const metrics = [
      { type: 'processing_time', value: data.processingTime },
      { type: 'api_calls', value: data.apiCalls },
      { type: 'cost', value: data.cost }
    ];
    
    for (const metric of metrics) {
      if (metric.value !== undefined) {
        await db.query(query, [sessionId, metric.type, metric.value]);
      }
    }
  }

  /**
   * Log session events for audit trail
   */
  private async logSessionEvent(
    sessionId: string, 
    eventType: string, 
    eventData: any
  ): Promise<void> {
    const query = `
      INSERT INTO session_events (session_id, event_type, event_data, created_at)
      VALUES ($1, $2, $3, NOW())
    `;
    
    try {
      await db.query(query, [sessionId, eventType, JSON.stringify(eventData)]);
    } catch (error) {
      // Don't fail the main operation if logging fails
      logger.warn('Failed to log session event', { error, sessionId, eventType });
    }
  }

  /**
   * Log execution events for audit trail
   */
  private async logExecutionEvent(
    executionId: string, 
    eventType: string, 
    eventData: any
  ): Promise<void> {
    const query = `
      INSERT INTO execution_events (execution_id, event_type, event_data, created_at)
      VALUES ($1, $2, $3, NOW())
    `;
    
    try {
      await db.query(query, [executionId, eventType, JSON.stringify(eventData)]);
    } catch (error) {
      // Don't fail the main operation if logging fails
      logger.warn('Failed to log execution event', { error, executionId, eventType });
    }
  }

  /**
   * Get next step number for a session
   */
  private async getNextStepNumber(sessionId: string): Promise<number> {
    const executions = await AgentExecutionModel.getBySessionId(sessionId);
    return executions.length + 1;
  }

  /**
   * Clean up old sessions and metrics (for maintenance)
   */
  async cleanupOldData(daysToKeep: number = 30): Promise<{ sessionsDeleted: number; metricsDeleted: number }> {
    const cutoffDate = new Date();
    cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
    
    const client = await db.connect();
    
    try {
      await client.query('BEGIN');
      
      // Delete old sessions and related data (cascade will handle related records)
      const sessionsResult = await client.query(
        'DELETE FROM agentic_rag_sessions WHERE created_at < $1',
        [cutoffDate]
      );
      
      // Delete orphaned quality metrics
      const metricsResult = await client.query(
        'DELETE FROM processing_quality_metrics WHERE created_at < $1',
        [cutoffDate]
      );
      
      await client.query('COMMIT');
      
      const sessionsDeleted = sessionsResult.rowCount || 0;
      const metricsDeleted = metricsResult.rowCount || 0;
      
      logger.info('Cleaned up old agentic RAG data', { 
        sessionsDeleted, 
        metricsDeleted, 
        cutoffDate 
      });
      
      return { sessionsDeleted, metricsDeleted };
    } catch (error) {
      await client.query('ROLLBACK');
      logger.error('Failed to cleanup old data', { error, daysToKeep });
      throw error;
    } finally {
      client.release();
    }
  }

  /**
   * Get analytics data for dashboard
   */
  async getAnalyticsData(days: number = 30): Promise<any> {
    const startDate = new Date();
    startDate.setDate(startDate.getDate() - days);
    
    // Get session statistics
    const sessionStats = await db.query(`
      SELECT 
        DATE(created_at) as date,
        COUNT(*) as total_sessions,
        COUNT(CASE WHEN status = 'completed' THEN 1 END) as successful_sessions,
        COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed_sessions,
        AVG(processing_time_ms) as avg_processing_time,
        AVG(total_cost) as avg_cost
      FROM agentic_rag_sessions 
      WHERE created_at >= $1
      GROUP BY DATE(created_at)
      ORDER BY date
    `, [startDate]);
    
    // Get agent performance
    const agentStats = await db.query(`
      SELECT 
        agent_name,
        COUNT(*) as total_executions,
        COUNT(CASE WHEN status = 'completed' THEN 1 END) as successful_executions,
        AVG(processing_time_ms) as avg_processing_time,
        AVG(retry_count) as avg_retries
      FROM agent_executions 
      WHERE created_at >= $1
      GROUP BY agent_name
    `, [startDate]);
    
    // Get quality metrics
    const qualityStats = await db.query(`
      SELECT 
        metric_type,
        AVG(metric_value) as avg_value,
        MIN(metric_value) as min_value,
        MAX(metric_value) as max_value
      FROM processing_quality_metrics 
      WHERE created_at >= $1
      GROUP BY metric_type
    `, [startDate]);
    
    return {
      sessionStats: sessionStats.rows,
      agentStats: agentStats.rows,
      qualityStats: qualityStats.rows,
      period: { startDate, endDate: new Date(), days }
    };
  }

  /**
   * Get analytics data for a specific document
   */
  async getDocumentAnalytics(documentId: string): Promise<any> {
    // Get all sessions for this document
    const sessions = await db.query(`
      SELECT 
        id,
        strategy,
        status,
        total_agents,
        completed_agents,
        failed_agents,
        overall_validation_score,
        processing_time_ms,
        api_calls_count,
        total_cost,
        created_at,
        completed_at
      FROM agentic_rag_sessions 
      WHERE document_id = $1
      ORDER BY created_at DESC
    `, [documentId]);
    
    // Get all executions for this document
    const executions = await db.query(`
      SELECT 
        ae.id,
        ae.agent_name,
        ae.step_number,
        ae.status,
        ae.processing_time_ms,
        ae.retry_count,
        ae.error_message,
        ae.created_at,
        ae.updated_at,
        ars.id as session_id
      FROM agent_executions ae
      JOIN agentic_rag_sessions ars ON ae.session_id = ars.id
      WHERE ars.document_id = $1
      ORDER BY ae.created_at DESC
    `, [documentId]);
    
    // Get quality metrics for this document
    const qualityMetrics = await db.query(`
      SELECT 
        pqm.id,
        pqm.metric_type,
        pqm.metric_value,
        pqm.metric_details,
        pqm.created_at,
        ars.id as session_id
      FROM processing_quality_metrics pqm
      JOIN agentic_rag_sessions ars ON pqm.session_id = ars.id
      WHERE ars.document_id = $1
      ORDER BY pqm.created_at DESC
    `, [documentId]);
    
    // Calculate summary statistics
    const totalSessions = sessions.rows.length;
    const successfulSessions = sessions.rows.filter((s: any) => s.status === 'completed').length;
    const totalProcessingTime = sessions.rows.reduce((sum: number, s: any) => sum + (s.processing_time_ms || 0), 0);
    const totalCost = sessions.rows.reduce((sum: number, s: any) => sum + (parseFloat(s.total_cost) || 0), 0);
    const avgValidationScore = sessions.rows
      .filter((s: any) => s.overall_validation_score !== null)
      .reduce((sum: number, s: any) => sum + parseFloat(s.overall_validation_score), 0) / 
      sessions.rows.filter((s: any) => s.overall_validation_score !== null).length || 0;
    
    return {
      documentId,
      summary: {
        totalSessions,
        successfulSessions,
        successRate: totalSessions > 0 ? (successfulSessions / totalSessions) * 100 : 0,
        totalProcessingTime,
        avgProcessingTime: totalSessions > 0 ? totalProcessingTime / totalSessions : 0,
        totalCost,
        avgCost: totalSessions > 0 ? totalCost / totalSessions : 0,
        avgValidationScore
      },
      sessions: sessions.rows,
      executions: executions.rows,
      qualityMetrics: qualityMetrics.rows
    };
  }
}

export const agenticRAGDatabaseService = new AgenticRAGDatabaseService(); 