//! Infrastructure-from-Code Detection Service
//!
//! Handles detection analysis and result persistence

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};

/// Detected technology information
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct DetectedTechnology {
    pub technology: String,
    pub confidence: f32,
    pub evidence: Vec<String>,
    pub version: Option<String>,
}

/// Detection result with analysis metadata
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct DetectionResult {
    pub id: String,
    pub project_path: String,
    pub organization: String,
    pub technologies: Vec<DetectedTechnology>,
    pub completeness: f32,
    pub confidence: f32,
    pub metadata: serde_json::Value,
    pub created_at: String,
    pub updated_at: String,
}

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

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

    /// List all detection results with pagination
    pub async fn list_detections(
        &self,
        page: u32,
        page_size: u32,
        organization: Option<String>,
    ) -> Result<(Vec<DetectionResult>, u32)> {
        let offset = (page - 1) * page_size;

        // Build query based on filters
        let (query, total_query) = if let Some(org) = organization {
            let org_escaped = org.replace("'", "\\'");
            (
                format!(
                    "SELECT * FROM detection_results WHERE organization = '{}' ORDER BY \
                     created_at DESC LIMIT {} START {}",
                    org_escaped, page_size, offset
                ),
                format!(
                    "SELECT count() FROM detection_results WHERE organization = '{}'",
                    org_escaped
                ),
            )
        } else {
            (
                format!(
                    "SELECT * FROM detection_results ORDER BY created_at DESC LIMIT {} START {}",
                    page_size, offset
                ),
                "SELECT count() FROM detection_results".to_string(),
            )
        };

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

        let detections: Vec<DetectionResult> = results.take(0).unwrap_or_default();

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

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

        Ok((detections, total_count))
    }

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

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

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

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

        let technologies_json =
            serde_json::to_string(&detection.technologies).unwrap_or_else(|_| "[]".to_string());
        let metadata_json =
            serde_json::to_string(&detection.metadata).unwrap_or_else(|_| "{}".to_string());

        let project_escaped = detection.project_path.replace("'", "\\'");
        let org_escaped = detection.organization.replace("'", "\\'");

        let query = format!(
            "CREATE detection_results SET id = <uuid>'{}', project_path = '{}', organization = \
             '{}', technologies = {}, completeness = {}, confidence = {}, metadata = {}, \
             created_at = <datetime>'{}', updated_at = <datetime>'{}'",
            id,
            project_escaped,
            org_escaped,
            technologies_json,
            detection.completeness,
            detection.confidence,
            metadata_json,
            detection.created_at,
            detection.updated_at
        );

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

        Ok(detection)
    }

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

        let technologies_json =
            serde_json::to_string(&detection.technologies).unwrap_or_else(|_| "[]".to_string());
        let metadata_json =
            serde_json::to_string(&detection.metadata).unwrap_or_else(|_| "{}".to_string());

        let id_escaped = id.replace("'", "\\'");
        let project_escaped = detection.project_path.replace("'", "\\'");
        let org_escaped = detection.organization.replace("'", "\\'");

        let query = format!(
            "UPDATE detection_results SET project_path = '{}', organization = '{}', technologies \
             = {}, completeness = {}, confidence = {}, metadata = {}, updated_at = <datetime>'{}' \
             WHERE id = '{}'",
            project_escaped,
            org_escaped,
            technologies_json,
            detection.completeness,
            detection.confidence,
            metadata_json,
            detection.updated_at,
            id_escaped
        );

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

        Ok(detection)
    }

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

        Ok(())
    }
}

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

    #[test]
    fn test_detection_creation() {
        let detection = DetectionResult {
            id: Uuid::new_v4().to_string(),
            project_path: "/home/user/project".to_string(),
            organization: "default".to_string(),
            technologies: vec![DetectedTechnology {
                technology: "python".to_string(),
                confidence: 0.95,
                evidence: vec!["requirements.txt".to_string()],
                version: Some("3.11".to_string()),
            }],
            completeness: 0.75,
            confidence: 0.85,
            metadata: serde_json::json!({}),
            created_at: chrono::Utc::now().to_rfc3339(),
            updated_at: chrono::Utc::now().to_rfc3339(),
        };

        assert_eq!(detection.technologies.len(), 1);
        assert_eq!(detection.technologies[0].technology, "python");
    }
}
