//! Infrastructure-from-Code Rules Service
//!
//! Manages inference rules for infrastructure completion

use std::sync::Arc;

use surrealdb::types::SurrealValue;

use anyhow::Context;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::DatabaseService;
use crate::error::{http, ControlCenterError, Result};

/// Single inference in a rule
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct RuleInference {
    pub technology: String,
    pub reason: String,
    pub confidence: f32,
    pub required: bool,
}

/// Inference rule for infrastructure completion
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct InferenceRule {
    pub id: String,
    pub name: String,
    pub organization: String,
    pub triggers: Vec<String>,
    pub inferences: Vec<RuleInference>,
    pub enabled: bool,
    pub priority: i32,
    pub created_at: String,
    pub updated_at: String,
}

/// Rules service for database operations
#[derive(Clone)]
pub struct IacRulesService {
    db: Arc<DatabaseService>,
}

impl IacRulesService {
    /// Create a new rules service
    pub fn new(db: Arc<DatabaseService>) -> Self {
        Self { db }
    }

    /// List all rules with pagination
    pub async fn list_rules(
        &self,
        page: u32,
        page_size: u32,
        enabled_only: Option<bool>,
        organization: Option<String>,
    ) -> Result<(Vec<InferenceRule>, u32)> {
        let offset = (page - 1) * page_size;

        // Build query with filters
        let mut where_clauses = Vec::new();

        if let Some(true) = enabled_only {
            where_clauses.push("enabled = true".to_string());
        }

        if let Some(org) = organization {
            where_clauses.push(format!("organization = '{}'", org.replace("'", "\\'")));
        }

        let where_clause = if where_clauses.is_empty() {
            String::new()
        } else {
            format!("WHERE {}", where_clauses.join(" AND "))
        };

        let query = format!(
            "SELECT * FROM inference_rules {} ORDER BY priority DESC, created_at DESC LIMIT {} \
             START {}",
            where_clause, page_size, offset
        );

        let total_query = format!("SELECT count() FROM inference_rules {}", where_clause);

        let mut results = self
            .db
            .db
            .query(&query)
            .await
            .context("Failed to list rules")?;

        let rules: Vec<InferenceRule> = results.take(0).unwrap_or_default();

        // Get total count
        let mut count_result = self
            .db
            .db
            .query(&total_query)
            .await
            .context("Failed to count rules")?;

        let total: Vec<u32> = count_result.take(0).unwrap_or_default();
        let total_count = total.into_iter().next().unwrap_or(0);

        Ok((rules, total_count))
    }

    /// Get a single rule by ID
    pub async fn get_rule(&self, id: &str) -> Result<InferenceRule> {
        let id_escaped = id.replace("'", "\\'");
        let mut result = self
            .db
            .db
            .query(format!(
                "SELECT * FROM inference_rules WHERE id = '{}'",
                id_escaped
            ))
            .await
            .context("Failed to get rule")?;

        let rules: Vec<InferenceRule> = result.take(0).unwrap_or_default();
        rules
            .into_iter()
            .next()
            .ok_or_else(|| ControlCenterError::Http(http::not_found("Rule not found")))
    }

    /// Create a new rule
    pub async fn create_rule(&self, mut rule: InferenceRule) -> Result<InferenceRule> {
        let id = Uuid::new_v4().to_string();
        let now = chrono::Utc::now().to_rfc3339();

        rule.id = id.clone();
        rule.created_at = now.clone();
        rule.updated_at = now;

        let triggers_json =
            serde_json::to_string(&rule.triggers).unwrap_or_else(|_| "[]".to_string());
        let inferences_json =
            serde_json::to_string(&rule.inferences).unwrap_or_else(|_| "[]".to_string());

        let name_escaped = rule.name.replace("'", "\\'");
        let org_escaped = rule.organization.replace("'", "\\'");

        let query = format!(
            "CREATE inference_rules SET id = <uuid>'{}', name = '{}', organization = '{}', \
             triggers = {}, inferences = {}, enabled = {}, priority = {}, created_at = \
             <datetime>'{}', updated_at = <datetime>'{}'",
            id,
            name_escaped,
            org_escaped,
            triggers_json,
            inferences_json,
            rule.enabled,
            rule.priority,
            rule.created_at,
            rule.updated_at
        );

        self.db
            .db
            .query(&query)
            .await
            .context("Failed to create rule")?;

        Ok(rule)
    }

    /// Update an existing rule
    pub async fn update_rule(&self, id: &str, mut rule: InferenceRule) -> Result<InferenceRule> {
        let now = chrono::Utc::now().to_rfc3339();
        rule.updated_at = now;

        let triggers_json =
            serde_json::to_string(&rule.triggers).unwrap_or_else(|_| "[]".to_string());
        let inferences_json =
            serde_json::to_string(&rule.inferences).unwrap_or_else(|_| "[]".to_string());

        let id_escaped = id.replace("'", "\\'");
        let name_escaped = rule.name.replace("'", "\\'");
        let org_escaped = rule.organization.replace("'", "\\'");

        let query = format!(
            "UPDATE inference_rules SET name = '{}', organization = '{}', triggers = {}, \
             inferences = {}, enabled = {}, priority = {}, updated_at = <datetime>'{}' WHERE id = \
             '{}'",
            name_escaped,
            org_escaped,
            triggers_json,
            inferences_json,
            rule.enabled,
            rule.priority,
            rule.updated_at,
            id_escaped
        );

        self.db
            .db
            .query(query)
            .await
            .context("Failed to update rule")?;

        Ok(rule)
    }

    /// Delete a rule
    pub async fn delete_rule(&self, id: &str) -> Result<()> {
        let id_escaped = id.replace("'", "\\'");
        self.db
            .db
            .query(format!(
                "DELETE FROM inference_rules WHERE id = '{}'",
                id_escaped
            ))
            .await
            .context("Failed to delete rule")?;

        Ok(())
    }

    /// Toggle rule enabled status
    pub async fn toggle_rule_enabled(&self, id: &str, enabled: bool) -> Result<()> {
        let now = chrono::Utc::now().to_rfc3339();
        let id_escaped = id.replace("'", "\\'");

        self.db
            .db
            .query(format!(
                "UPDATE inference_rules SET enabled = {}, updated_at = <datetime>'{}' WHERE id = \
                 '{}'",
                enabled, now, id_escaped
            ))
            .await
            .context("Failed to toggle rule enabled status")?;

        Ok(())
    }
}

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

    #[test]
    fn test_rule_creation() {
        let rule = InferenceRule {
            id: Uuid::new_v4().to_string(),
            name: "Python Dependencies".to_string(),
            organization: "default".to_string(),
            triggers: vec!["python".to_string()],
            inferences: vec![RuleInference {
                technology: "postgresql".to_string(),
                reason: "Python applications often require a database".to_string(),
                confidence: 0.8,
                required: true,
            }],
            enabled: true,
            priority: 10,
            created_at: chrono::Utc::now().to_rfc3339(),
            updated_at: chrono::Utc::now().to_rfc3339(),
        };

        assert_eq!(rule.triggers.len(), 1);
        assert_eq!(rule.inferences.len(), 1);
        assert!(rule.enabled);
    }
}
