// vapora-knowledge-graph: Analytics module for KG insights
// Phase 5.5: Advanced persistence analytics, reporting, and trends

use std::collections::HashMap;

use async_trait::async_trait;
use serde::{Deserialize, Serialize};

use crate::metrics::{AnalyticsComputation, TimePeriod};
use crate::persistence::PersistedExecution;

/// Time-windowed performance metrics for an agent
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentPerformance {
    pub agent_id: String,
    pub period: TimePeriod,
    pub tasks_executed: u32,
    pub tasks_successful: u32,
    pub success_rate: f64,
    pub avg_duration_ms: f64,
    pub min_duration_ms: u64,
    pub max_duration_ms: u64,
    pub total_input_tokens: u64,
    pub total_output_tokens: u64,
    pub avg_tokens_per_task: f64,
}

/// Task type analytics with trend data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskTypeAnalytics {
    pub task_type: String,
    pub total_executions: u32,
    pub successful_executions: u32,
    pub success_rate: f64,
    pub agents_involved: Vec<String>,
    pub avg_duration_ms: f64,
    pub total_tokens_used: u64,
    pub quality_score: f64, // Derived from success_rate
}

/// Agent learning progress over time
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearningProgress {
    pub agent_id: String,
    pub task_type: String,
    pub initial_success_rate: f64,
    pub current_success_rate: f64,
    pub improvement: f64,         // Percentage points
    pub tasks_to_confidence: u32, // Executions needed for 95% confidence
    pub trend: PerformanceTrend,
}

/// Performance trend direction
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum PerformanceTrend {
    Improving,
    Stable,
    Declining,
}

/// Cost efficiency report for provider selection
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostEfficiencyReport {
    pub period: TimePeriod,
    pub total_cost_cents: u32,
    pub total_tasks: u32,
    pub cost_per_task_cents: f32,
    pub most_used_provider: String,
    pub most_efficient_provider: String,
    pub provider_costs: HashMap<String, ProviderCostBreakdown>,
}

/// Cost breakdown by provider
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderCostBreakdown {
    pub provider: String,
    pub tasks_executed: u32,
    pub total_cost_cents: u32,
    pub cost_per_task_cents: f32,
    pub input_tokens: u64,
    pub output_tokens: u64,
}

/// System-wide analytics dashboard data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DashboardMetrics {
    pub period: TimePeriod,
    pub total_tasks: u32,
    pub successful_tasks: u32,
    pub overall_success_rate: f64,
    pub total_agents_active: u32,
    pub total_task_types: u32,
    pub avg_task_duration_ms: f64,
    pub total_cost_cents: u32,
    pub top_performing_agents: Vec<(String, f64)>, // (agent_id, success_rate)
    pub top_task_types: Vec<(String, u32)>,        // (task_type, count)
}

/// Query service for KG analytics
pub struct KGAnalytics;

impl KGAnalytics {
    /// Calculate agent performance for a time period
    pub fn calculate_agent_performance(
        executions: Vec<PersistedExecution>,
        period: TimePeriod,
        agent_id: &str,
    ) -> AgentPerformance {
        if executions.is_empty() {
            return AgentPerformance {
                agent_id: agent_id.to_string(),
                period,
                tasks_executed: 0,
                tasks_successful: 0,
                success_rate: 0.5,
                avg_duration_ms: 0.0,
                min_duration_ms: 0,
                max_duration_ms: 0,
                total_input_tokens: 0,
                total_output_tokens: 0,
                avg_tokens_per_task: 0.0,
            };
        }

        let successful = executions.iter().filter(|e| e.outcome == "success").count() as u32;

        let total_duration: u64 = executions.iter().map(|e| e.duration_ms).sum();
        let avg_duration = total_duration as f64 / executions.len() as f64;

        let min_duration = executions.iter().map(|e| e.duration_ms).min().unwrap_or(0);
        let max_duration = executions.iter().map(|e| e.duration_ms).max().unwrap_or(0);

        let total_input_tokens: u64 = executions.iter().map(|e| e.input_tokens).sum();
        let total_output_tokens: u64 = executions.iter().map(|e| e.output_tokens).sum();
        let avg_tokens =
            (total_input_tokens + total_output_tokens) as f64 / executions.len() as f64;

        AgentPerformance {
            agent_id: agent_id.to_string(),
            period,
            tasks_executed: executions.len() as u32,
            tasks_successful: successful,
            success_rate: successful as f64 / executions.len() as f64,
            avg_duration_ms: avg_duration,
            min_duration_ms: min_duration,
            max_duration_ms: max_duration,
            total_input_tokens,
            total_output_tokens,
            avg_tokens_per_task: avg_tokens,
        }
    }

    /// Analyze task type performance across agents
    pub fn analyze_task_type(
        executions: Vec<PersistedExecution>,
        task_type: &str,
    ) -> TaskTypeAnalytics {
        if executions.is_empty() {
            return TaskTypeAnalytics {
                task_type: task_type.to_string(),
                total_executions: 0,
                successful_executions: 0,
                success_rate: 0.5,
                agents_involved: Vec::new(),
                avg_duration_ms: 0.0,
                total_tokens_used: 0,
                quality_score: 0.5,
            };
        }

        let successful = executions.iter().filter(|e| e.outcome == "success").count() as u32;

        let total_duration: u64 = executions.iter().map(|e| e.duration_ms).sum();
        let avg_duration = total_duration as f64 / executions.len() as f64;

        let total_tokens: u64 = executions
            .iter()
            .map(|e| e.input_tokens + e.output_tokens)
            .sum();

        let mut agents = executions
            .iter()
            .map(|e| e.agent_id.clone())
            .collect::<Vec<_>>();
        agents.sort();
        agents.dedup();

        let success_rate = successful as f64 / executions.len() as f64;

        TaskTypeAnalytics {
            task_type: task_type.to_string(),
            total_executions: executions.len() as u32,
            successful_executions: successful,
            success_rate,
            agents_involved: agents,
            avg_duration_ms: avg_duration,
            total_tokens_used: total_tokens,
            quality_score: success_rate, // Quality derived from success rate
        }
    }

    /// Calculate learning progress for agent + task type
    pub fn calculate_learning_progress(
        recent_executions: Vec<PersistedExecution>,
        _historical_executions: Vec<PersistedExecution>,
        agent_id: &str,
        task_type: &str,
    ) -> LearningProgress {
        if recent_executions.is_empty() {
            return LearningProgress {
                agent_id: agent_id.to_string(),
                task_type: task_type.to_string(),
                initial_success_rate: 0.5,
                current_success_rate: 0.5,
                improvement: 0.0,
                tasks_to_confidence: 20,
                trend: PerformanceTrend::Stable,
            };
        }

        let recent_successful = recent_executions
            .iter()
            .filter(|e| e.outcome == "success")
            .count() as u32;

        let current_success_rate = recent_successful as f64 / recent_executions.len() as f64;

        // Determine trend: compare first half vs second half of recent executions
        let midpoint = recent_executions.len() / 2;
        let trend = if midpoint > 0 {
            let first_half_success = recent_executions[..midpoint]
                .iter()
                .filter(|e| e.outcome == "success")
                .count() as f64
                / midpoint as f64;

            let second_half_success = recent_executions[midpoint..]
                .iter()
                .filter(|e| e.outcome == "success")
                .count() as f64
                / (recent_executions.len() - midpoint) as f64;

            if second_half_success > first_half_success + 0.05 {
                PerformanceTrend::Improving
            } else if second_half_success < first_half_success - 0.05 {
                PerformanceTrend::Declining
            } else {
                PerformanceTrend::Stable
            }
        } else {
            PerformanceTrend::Stable
        };

        LearningProgress {
            agent_id: agent_id.to_string(),
            task_type: task_type.to_string(),
            initial_success_rate: 0.5,
            current_success_rate,
            improvement: (current_success_rate - 0.5) * 100.0,
            tasks_to_confidence: 20 - recent_executions.len().min(20) as u32,
            trend,
        }
    }

    /// Generate cost efficiency report
    pub fn generate_cost_report(
        executions: Vec<PersistedExecution>,
        period: TimePeriod,
    ) -> CostEfficiencyReport {
        if executions.is_empty() {
            return CostEfficiencyReport {
                period,
                total_cost_cents: 0,
                total_tasks: 0,
                cost_per_task_cents: 0.0,
                most_used_provider: "unknown".to_string(),
                most_efficient_provider: "unknown".to_string(),
                provider_costs: HashMap::new(),
            };
        }

        let mut provider_costs: HashMap<String, ProviderCostBreakdown> = HashMap::new();
        let mut total_cost_cents = 0u32;

        for exec in &executions {
            // Estimate cost (simplified: assume 1 cent per token for demo)
            let cost_cents = (exec.input_tokens + exec.output_tokens) as u32 / 1000;
            total_cost_cents = total_cost_cents.saturating_add(cost_cents);

            let entry = provider_costs
                .entry("unknown_provider".to_string())
                .or_insert(ProviderCostBreakdown {
                    provider: "unknown_provider".to_string(),
                    tasks_executed: 0,
                    total_cost_cents: 0,
                    cost_per_task_cents: 0.0,
                    input_tokens: 0,
                    output_tokens: 0,
                });

            entry.tasks_executed += 1;
            entry.total_cost_cents = entry.total_cost_cents.saturating_add(cost_cents);
            entry.input_tokens += exec.input_tokens;
            entry.output_tokens += exec.output_tokens;
        }

        // Calculate cost per task for each provider
        for breakdown in provider_costs.values_mut() {
            breakdown.cost_per_task_cents = if breakdown.tasks_executed > 0 {
                breakdown.total_cost_cents as f32 / breakdown.tasks_executed as f32
            } else {
                0.0
            };
        }

        let most_used_provider = provider_costs
            .iter()
            .max_by_key(|(_, b)| b.tasks_executed)
            .map(|(p, _)| p.clone())
            .unwrap_or_else(|| "unknown".to_string());

        let most_efficient_provider = provider_costs
            .iter()
            .min_by(|(_p1, b1), (_p2, b2)| {
                b1.cost_per_task_cents
                    .partial_cmp(&b2.cost_per_task_cents)
                    .unwrap_or(std::cmp::Ordering::Equal)
            })
            .map(|(p, _)| p.clone())
            .unwrap_or_else(|| "unknown".to_string());

        CostEfficiencyReport {
            period,
            total_cost_cents,
            total_tasks: executions.len() as u32,
            cost_per_task_cents: if executions.is_empty() {
                0.0
            } else {
                total_cost_cents as f32 / executions.len() as f32
            },
            most_used_provider,
            most_efficient_provider,
            provider_costs,
        }
    }

    /// Generate overall system dashboard metrics
    pub fn generate_dashboard_metrics(
        executions: Vec<PersistedExecution>,
        period: TimePeriod,
    ) -> DashboardMetrics {
        if executions.is_empty() {
            return DashboardMetrics {
                period,
                total_tasks: 0,
                successful_tasks: 0,
                overall_success_rate: 0.5,
                total_agents_active: 0,
                total_task_types: 0,
                avg_task_duration_ms: 0.0,
                total_cost_cents: 0,
                top_performing_agents: Vec::new(),
                top_task_types: Vec::new(),
            };
        }

        let successful_tasks = executions.iter().filter(|e| e.outcome == "success").count() as u32;

        let total_duration: u64 = executions.iter().map(|e| e.duration_ms).sum();
        let avg_duration = total_duration as f64 / executions.len() as f64;

        // Collect unique agents and task types
        let mut agents: Vec<String> = executions.iter().map(|e| e.agent_id.clone()).collect();
        agents.sort();
        agents.dedup();

        let mut task_types: HashMap<String, u32> = HashMap::new();
        for exec in &executions {
            *task_types.entry(exec.task_type.clone()).or_insert(0) += 1;
        }

        let task_type_count = task_types.len() as u32;
        let mut top_task_types: Vec<(String, u32)> = task_types.into_iter().collect();
        top_task_types.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
        top_task_types.truncate(5);

        // Calculate cost (simplified)
        let total_cost_cents: u32 = executions
            .iter()
            .map(|e| ((e.input_tokens + e.output_tokens) / 1000) as u32)
            .sum();

        DashboardMetrics {
            period,
            total_tasks: executions.len() as u32,
            successful_tasks,
            overall_success_rate: successful_tasks as f64 / executions.len() as f64,
            total_agents_active: agents.len() as u32,
            total_task_types: task_type_count,
            avg_task_duration_ms: avg_duration,
            total_cost_cents,
            top_performing_agents: Vec::new(), // Computed separately
            top_task_types,
        }
    }
}

#[cfg(test)]
mod tests {
    use chrono::Utc;

    use super::*;

    fn create_test_execution(
        agent_id: &str,
        task_type: &str,
        success: bool,
        duration_ms: u64,
    ) -> PersistedExecution {
        PersistedExecution {
            execution_id: uuid::Uuid::new_v4().to_string(),
            task_description: "test".to_string(),
            agent_id: agent_id.to_string(),
            agent_role: None,
            provider: "claude".to_string(),
            cost_cents: 100,
            outcome: if success { "success" } else { "failure" }.to_string(),
            duration_ms,
            input_tokens: 5000,
            output_tokens: 2000,
            task_type: task_type.to_string(),
            error_message: None,
            solution: None,
            embedding: vec![0.1; 1536],
            executed_at: Utc::now().to_rfc3339(),
            created_at: Utc::now().to_rfc3339(),
        }
    }

    #[test]
    fn test_agent_performance_calculation() {
        let executions = vec![
            create_test_execution("agent-1", "coding", true, 1000),
            create_test_execution("agent-1", "coding", true, 2000),
            create_test_execution("agent-1", "coding", false, 1500),
        ];

        let perf =
            KGAnalytics::calculate_agent_performance(executions, TimePeriod::LastDay, "agent-1");

        assert_eq!(perf.tasks_executed, 3);
        assert_eq!(perf.tasks_successful, 2);
        assert!((perf.success_rate - 2.0 / 3.0).abs() < 0.01);
        assert!((perf.avg_duration_ms - 1500.0).abs() < 0.01);
    }

    #[test]
    fn test_task_type_analysis() {
        let executions = vec![
            create_test_execution("agent-1", "coding", true, 1000),
            create_test_execution("agent-2", "coding", true, 1200),
            create_test_execution("agent-1", "coding", false, 1100),
        ];

        let analysis = KGAnalytics::analyze_task_type(executions, "coding");

        assert_eq!(analysis.task_type, "coding");
        assert_eq!(analysis.total_executions, 3);
        assert_eq!(analysis.successful_executions, 2);
        assert!((analysis.success_rate - 2.0 / 3.0).abs() < 0.01);
        assert_eq!(analysis.agents_involved.len(), 2);
    }

    #[test]
    fn test_learning_progress_calculation() {
        let executions = vec![
            create_test_execution("agent-1", "coding", false, 1000),
            create_test_execution("agent-1", "coding", true, 1100),
            create_test_execution("agent-1", "coding", true, 1200),
            create_test_execution("agent-1", "coding", true, 1300),
        ];

        let progress =
            KGAnalytics::calculate_learning_progress(executions, Vec::new(), "agent-1", "coding");

        assert_eq!(progress.agent_id, "agent-1");
        assert!((progress.current_success_rate - 0.75).abs() < 0.01);
        assert!(progress.improvement > 0.0);
    }

    #[test]
    fn test_dashboard_metrics() {
        let executions = vec![
            create_test_execution("agent-1", "coding", true, 1000),
            create_test_execution("agent-2", "review", true, 1100),
            create_test_execution("agent-1", "coding", false, 1200),
        ];

        let metrics = KGAnalytics::generate_dashboard_metrics(executions, TimePeriod::LastDay);

        assert_eq!(metrics.total_tasks, 3);
        assert_eq!(metrics.successful_tasks, 2);
        assert!((metrics.overall_success_rate - 2.0 / 3.0).abs() < 0.01);
        assert_eq!(metrics.total_agents_active, 2);
        assert_eq!(metrics.total_task_types, 2);
    }

    #[test]
    fn test_cost_report_generation() {
        let executions = vec![
            create_test_execution("agent-1", "coding", true, 1000),
            create_test_execution("agent-1", "coding", true, 1100),
        ];

        let report = KGAnalytics::generate_cost_report(executions, TimePeriod::LastDay);

        assert_eq!(report.total_tasks, 2);
        assert!(report.total_cost_cents > 0);
        assert!(report.cost_per_task_cents > 0.0);
    }

    #[test]
    fn test_empty_executions() {
        let perf =
            KGAnalytics::calculate_agent_performance(Vec::new(), TimePeriod::LastDay, "agent-1");
        assert_eq!(perf.tasks_executed, 0);
        assert_eq!(perf.success_rate, 0.5);
    }

    #[test]
    fn test_time_period_descriptions() {
        assert_eq!(TimePeriod::LastHour.description(), "Last Hour");
        assert_eq!(TimePeriod::LastDay.description(), "Last 24 Hours");
        assert_eq!(TimePeriod::LastWeek.description(), "Last 7 Days");
        assert_eq!(TimePeriod::LastMonth.description(), "Last 30 Days");
        assert_eq!(TimePeriod::AllTime.description(), "All Time");
    }
}

/// Implement AnalyticsComputation trait for KGAnalytics
#[async_trait]
impl AnalyticsComputation for KGAnalytics {
    async fn compute_agent_performance(
        &self,
        executions: Vec<PersistedExecution>,
        period: crate::metrics::TimePeriod,
        agent_id: &str,
    ) -> anyhow::Result<AgentPerformance> {
        Ok(KGAnalytics::calculate_agent_performance(
            executions, period, agent_id,
        ))
    }

    async fn compute_task_type_analytics(
        &self,
        executions: Vec<PersistedExecution>,
        task_type: &str,
    ) -> anyhow::Result<TaskTypeAnalytics> {
        Ok(KGAnalytics::analyze_task_type(executions, task_type))
    }

    async fn compute_dashboard_metrics(
        &self,
        executions: Vec<PersistedExecution>,
        period: crate::metrics::TimePeriod,
    ) -> anyhow::Result<DashboardMetrics> {
        Ok(KGAnalytics::generate_dashboard_metrics(executions, period))
    }

    async fn compute_cost_report(
        &self,
        executions: Vec<PersistedExecution>,
        period: crate::metrics::TimePeriod,
    ) -> anyhow::Result<CostEfficiencyReport> {
        Ok(KGAnalytics::generate_cost_report(executions, period))
    }

    async fn compute_learning_progress(
        &self,
        recent_executions: Vec<PersistedExecution>,
        historical_executions: Vec<PersistedExecution>,
        agent_id: &str,
        task_type: &str,
    ) -> anyhow::Result<LearningProgress> {
        Ok(KGAnalytics::calculate_learning_progress(
            recent_executions,
            historical_executions,
            agent_id,
            task_type,
        ))
    }
}
