// KG Persistence Layer
// Phase 5.5: Persist execution history to SurrealDB for durability and
// analytics

use std::sync::Arc;

use chrono::Utc;
use serde::{Deserialize, Serialize};
use surrealdb::engine::remote::ws::Client;
use surrealdb::Surreal;
use tracing::debug;

use crate::metrics::{AnalyticsComputation, TimePeriod};
use crate::models::ExecutionRecord;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PersistedExecution {
    pub execution_id: String,
    pub task_description: String,
    pub agent_id: String,
    pub agent_role: Option<String>,
    pub provider: String,
    pub cost_cents: u32,
    pub outcome: String,
    pub duration_ms: u64,
    pub input_tokens: u64,
    pub output_tokens: u64,
    pub task_type: String,
    pub error_message: Option<String>,
    pub solution: Option<String>,
    pub embedding: Vec<f32>,
    pub executed_at: String,
    pub created_at: String,
}

impl PersistedExecution {
    /// Convert from ExecutionRecord and embedding
    pub fn from_execution_record(record: &ExecutionRecord, embedding: Vec<f32>) -> Self {
        Self {
            execution_id: record.id.clone(),
            task_description: record.description.clone(),
            agent_id: record.agent_id.clone(),
            agent_role: record.agent_role.clone(),
            provider: record.provider.clone(),
            cost_cents: record.cost_cents,
            outcome: if record.success {
                "success".to_string()
            } else {
                "failure".to_string()
            },
            duration_ms: record.duration_ms,
            input_tokens: record.input_tokens,
            output_tokens: record.output_tokens,
            task_type: record.task_type.clone(),
            error_message: record.error.clone(),
            solution: record.solution.clone(),
            embedding,
            executed_at: record.timestamp.to_rfc3339(),
            created_at: Utc::now().to_rfc3339(),
        }
    }
}

pub struct KGPersistence {
    db: Arc<Surreal<Client>>,
    analytics: Option<Arc<dyn AnalyticsComputation>>,
}

impl std::fmt::Debug for KGPersistence {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("KGPersistence")
            .field("db", &"<SurrealDB>")
            .field("analytics", &"<AnalyticsComputation>")
            .finish()
    }
}

impl KGPersistence {
    /// Create new persistence layer
    pub fn new(db: Surreal<Client>) -> Self {
        Self {
            db: Arc::new(db),
            analytics: None,
        }
    }

    /// Create with analytics computation provider
    pub fn with_analytics(db: Surreal<Client>, analytics: Arc<dyn AnalyticsComputation>) -> Self {
        Self {
            db: Arc::new(db),
            analytics: Some(analytics),
        }
    }

    /// Set analytics computation provider
    pub fn set_analytics(&mut self, analytics: Arc<dyn AnalyticsComputation>) {
        self.analytics = Some(analytics);
    }

    /// Persist a single execution record
    pub async fn persist_execution(&self, execution: PersistedExecution) -> anyhow::Result<()> {
        debug!(
            "Persisting execution {} for agent {}",
            execution.execution_id, execution.agent_id
        );

        let _: Option<serde_json::Value> = self
            .db
            .create::<Option<serde_json::Value>>("kg_executions")
            .content(serde_json::to_value(&execution)?)
            .await?;

        Ok(())
    }

    /// Persist multiple execution records (batch operation)
    pub async fn persist_executions(
        &self,
        executions: Vec<PersistedExecution>,
    ) -> anyhow::Result<()> {
        if executions.is_empty() {
            return Ok(());
        }

        debug!("Persisting {} executions in batch", executions.len());

        for execution in executions {
            self.persist_execution(execution).await?;
        }

        Ok(())
    }

    /// Load historical executions for similar task (using vector similarity)
    pub async fn find_similar_executions(
        &self,
        _embedding: &[f32],
        limit: usize,
    ) -> anyhow::Result<Vec<PersistedExecution>> {
        debug!("Searching for similar executions (limit: {})", limit);

        // SurrealDB vector similarity queries require different syntax
        // For now, return recent successful executions
        let query = format!(
            "SELECT * FROM kg_executions WHERE outcome = 'success' LIMIT {}",
            limit
        );

        let mut response = self.db.query(&query).await?;
        let results: Vec<PersistedExecution> = response.take(0)?;
        Ok(results)
    }

    /// Get success rate by agent
    pub async fn get_agent_success_rate(&self, agent_id: &str) -> anyhow::Result<f64> {
        debug!("Fetching success rate for agent {}", agent_id);

        let query = format!(
            "SELECT count(SELECT outcome FROM kg_executions WHERE outcome = 'success' AND \
             agent_id = '{}') * 100.0 / count(SELECT * FROM kg_executions WHERE agent_id = '{}') \
             AS rate FROM kg_executions",
            agent_id, agent_id
        );

        let mut response = self.db.query(&query).await?;

        #[derive(Deserialize)]
        struct RateResult {
            rate: Option<f64>,
        }

        let result: Vec<RateResult> = response.take(0)?;
        Ok(result.first().and_then(|r| r.rate).unwrap_or(0.0))
    }

    /// Get task type distribution
    pub async fn get_task_distribution(&self) -> anyhow::Result<Vec<(String, u64, f64)>> {
        debug!("Fetching task type distribution");

        let query = "SELECT task_type, count(*) as count FROM kg_executions GROUP BY task_type";

        let mut response = self.db.query(query).await?;

        #[derive(Deserialize)]
        struct DistResult {
            task_type: String,
            count: u64,
        }

        let results: Vec<DistResult> = response.take(0)?;
        Ok(results
            .into_iter()
            .map(|r| (r.task_type, r.count, 0.0))
            .collect())
    }

    /// Cleanup old executions (keep only last N days)
    pub async fn cleanup_old_executions(&self, days: i32) -> anyhow::Result<u64> {
        debug!("Cleaning up executions older than {} days", days);

        let cutoff = Utc::now() - chrono::Duration::days(days as i64);
        let cutoff_str = cutoff.to_rfc3339();

        let query = format!(
            "DELETE FROM kg_executions WHERE executed_at < '{}'",
            cutoff_str
        );

        let mut response = self.db.query(&query).await?;

        #[derive(Deserialize)]
        #[allow(dead_code)]
        struct DeleteResult {
            deleted: Option<u64>,
        }

        let _result: Vec<DeleteResult> = response.take(0)?;
        Ok(0) // SurrealDB 2.3 doesn't return delete count easily
    }

    /// Get total execution count
    pub async fn get_execution_count(&self) -> anyhow::Result<u64> {
        debug!("Fetching execution count");

        let query = "SELECT count(*) as total FROM kg_executions";
        let mut response = self.db.query(query).await?;

        #[derive(Deserialize)]
        #[allow(dead_code)]
        struct CountResult {
            total: u64,
        }

        let result: Vec<CountResult> = response.take(0)?;
        Ok(result.first().map(|r| r.total).unwrap_or(0))
    }

    /// Get task-type specific executions for agent (for learning profiles).
    /// Returns executions filtered by agent_id and task_type, limited to recent
    /// data.
    pub async fn get_executions_for_task_type(
        &self,
        agent_id: &str,
        task_type: &str,
        limit: usize,
    ) -> anyhow::Result<Vec<PersistedExecution>> {
        debug!(
            "Fetching executions for agent {} task_type {} (limit: {})",
            agent_id, task_type, limit
        );

        let query = format!(
            "SELECT * FROM kg_executions WHERE agent_id = '{}' AND task_type = '{}' ORDER BY \
             executed_at DESC LIMIT {}",
            agent_id, task_type, limit
        );

        let mut response = self.db.query(&query).await?;
        let results: Vec<PersistedExecution> = response.take(0)?;
        Ok(results)
    }

    /// Get all recent executions for agent across all task types.
    /// Useful for computing overall success rate and expertise.
    pub async fn get_agent_executions(
        &self,
        agent_id: &str,
        limit: usize,
    ) -> anyhow::Result<Vec<PersistedExecution>> {
        debug!(
            "Fetching all executions for agent {} (limit: {})",
            agent_id, limit
        );

        let query = format!(
            "SELECT * FROM kg_executions WHERE agent_id = '{}' ORDER BY executed_at DESC LIMIT {}",
            agent_id, limit
        );

        let mut response = self.db.query(&query).await?;
        let results: Vec<PersistedExecution> = response.take(0)?;
        Ok(results)
    }

    /// Record analytics event
    pub async fn record_event(
        &self,
        event_type: &str,
        agent_id: &str,
        metric_name: &str,
        metric_value: f64,
    ) -> anyhow::Result<()> {
        debug!(
            "Recording event: {} for agent {} ({}={})",
            event_type, agent_id, metric_name, metric_value
        );

        let event_id = uuid::Uuid::new_v4().to_string();
        let now = Utc::now().to_rfc3339();

        let event = serde_json::json!({
            "event_id": event_id,
            "event_type": event_type,
            "agent_id": agent_id,
            "metric_name": metric_name,
            "metric_value": metric_value,
            "recorded_at": now,
            "created_at": now,
        });

        let _: Option<serde_json::Value> = self
            .db
            .create::<Option<serde_json::Value>>("analytics_events")
            .content(event)
            .await?;

        Ok(())
    }

    /// Get agent performance metrics for a specific time period
    pub async fn get_agent_performance(
        &self,
        agent_id: &str,
        period: TimePeriod,
    ) -> anyhow::Result<crate::analytics::AgentPerformance> {
        // Fetch executions for agent in the given period
        let executions = self.get_agent_executions(agent_id, 1000).await?;

        // Filter by time period
        let cutoff = Utc::now() - period.duration();
        let filtered: Vec<PersistedExecution> = executions
            .into_iter()
            .filter(|e| {
                if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(&e.executed_at) {
                    dt.with_timezone(&Utc) > cutoff
                } else {
                    false
                }
            })
            .collect();

        if let Some(analytics) = &self.analytics {
            analytics
                .compute_agent_performance(filtered, period, agent_id)
                .await
        } else {
            anyhow::bail!("Analytics computation provider not set")
        }
    }

    /// Get task type analytics across all agents
    pub async fn get_task_type_analytics(
        &self,
        task_type: &str,
        period: TimePeriod,
    ) -> anyhow::Result<crate::analytics::TaskTypeAnalytics> {
        // Fetch executions for task type
        let query = format!(
            "SELECT * FROM kg_executions WHERE task_type = '{}' ORDER BY executed_at DESC LIMIT \
             1000",
            task_type
        );

        let mut response = self.db.query(&query).await?;
        let executions: Vec<PersistedExecution> = response.take(0)?;

        // Filter by time period
        let cutoff = Utc::now() - period.duration();
        let filtered: Vec<PersistedExecution> = executions
            .into_iter()
            .filter(|e| {
                if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(&e.executed_at) {
                    dt.with_timezone(&Utc) > cutoff
                } else {
                    false
                }
            })
            .collect();

        if let Some(analytics) = &self.analytics {
            analytics
                .compute_task_type_analytics(filtered, task_type)
                .await
        } else {
            anyhow::bail!("Analytics computation provider not set")
        }
    }

    /// Get comprehensive dashboard metrics for the system
    pub async fn get_dashboard_metrics(
        &self,
        period: TimePeriod,
    ) -> anyhow::Result<crate::analytics::DashboardMetrics> {
        // Fetch all recent executions
        let query = "SELECT * FROM kg_executions ORDER BY executed_at DESC LIMIT 5000";
        let mut response = self.db.query(query).await?;
        let executions: Vec<PersistedExecution> = response.take(0)?;

        // Filter by time period
        let cutoff = Utc::now() - period.duration();
        let filtered: Vec<PersistedExecution> = executions
            .into_iter()
            .filter(|e| {
                if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(&e.executed_at) {
                    dt.with_timezone(&Utc) > cutoff
                } else {
                    false
                }
            })
            .collect();

        if let Some(analytics) = &self.analytics {
            analytics.compute_dashboard_metrics(filtered, period).await
        } else {
            anyhow::bail!("Analytics computation provider not set")
        }
    }

    /// Get cost efficiency report for the period
    pub async fn get_cost_report(
        &self,
        period: TimePeriod,
    ) -> anyhow::Result<crate::analytics::CostEfficiencyReport> {
        // Fetch all recent executions
        let query = "SELECT * FROM kg_executions ORDER BY executed_at DESC LIMIT 5000";
        let mut response = self.db.query(query).await?;
        let executions: Vec<PersistedExecution> = response.take(0)?;

        // Filter by time period
        let cutoff = Utc::now() - period.duration();
        let filtered: Vec<PersistedExecution> = executions
            .into_iter()
            .filter(|e| {
                if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(&e.executed_at) {
                    dt.with_timezone(&Utc) > cutoff
                } else {
                    false
                }
            })
            .collect();

        if let Some(analytics) = &self.analytics {
            analytics.compute_cost_report(filtered, period).await
        } else {
            anyhow::bail!("Analytics computation provider not set")
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_persisted_execution_creation() {
        let record = ExecutionRecord {
            id: "exec-1".to_string(),
            task_id: "task-1".to_string(),
            agent_id: "agent-1".to_string(),
            agent_role: None,
            task_type: "coding".to_string(),
            description: "Write code".to_string(),
            duration_ms: 5000,
            input_tokens: 100,
            output_tokens: 250,
            cost_cents: 75,
            provider: "claude".to_string(),
            success: true,
            error: None,
            solution: Some("Use async/await".to_string()),
            root_cause: None,
            timestamp: Utc::now(),
        };

        let embedding = vec![0.1; 1536];
        let persisted = PersistedExecution::from_execution_record(&record, embedding.clone());

        assert_eq!(persisted.execution_id, "exec-1");
        assert_eq!(persisted.agent_id, "agent-1");
        assert_eq!(persisted.outcome, "success");
        assert_eq!(persisted.embedding.len(), 1536);
    }
}
