//! Infrastructure-from-Code Deployment Service
//!
//! Manages deployment plans and execution tracking

use std::sync::Arc;

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

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

/// Deployment task status
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
#[serde(rename_all = "lowercase")]
pub enum TaskStatus {
    #[default]
    Pending,
    Running,
    Completed,
    Failed,
}

impl std::fmt::Display for TaskStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TaskStatus::Pending => write!(f, "pending"),
            TaskStatus::Running => write!(f, "running"),
            TaskStatus::Completed => write!(f, "completed"),
            TaskStatus::Failed => write!(f, "failed"),
        }
    }
}

/// Deployment task
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct DeploymentTask {
    pub id: String,
    pub task_type: String,
    pub name: String,
    pub description: String,
    pub depends_on: Vec<String>,
    pub parameters: serde_json::Value,
    pub status: TaskStatus,
    pub estimated_duration_seconds: Option<u32>,
}

/// Deployment plan
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct DeploymentPlan {
    pub id: String,
    pub name: String,
    pub description: String,
    pub organization: String,
    pub tasks: Vec<DeploymentTask>,
    pub created_at: String,
    pub scheduled_at: Option<String>,
}

/// Deployment execution status
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct DeploymentExecution {
    pub id: String,
    pub plan_id: String,
    pub workflow_id: Option<String>,
    pub status: String,
    pub progress_percentage: u32,
    pub completed_tasks: u32,
    pub total_tasks: u32,
    pub started_at: Option<String>,
    pub completed_at: Option<String>,
}

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

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

    /// List all deployment plans with pagination
    pub async fn list_deployments(
        &self,
        page: u32,
        page_size: u32,
        status: Option<String>,
        organization: Option<String>,
    ) -> Result<(Vec<DeploymentPlan>, u32)> {
        let offset = (page - 1) * page_size;

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

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

        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 deployment_plans {} ORDER BY created_at DESC LIMIT {} START {}",
            where_clause, page_size, offset
        );

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

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

        let plans: Vec<DeploymentPlan> = results.take(0).unwrap_or_default();

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

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

        Ok((plans, total_count))
    }

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

        let plans: Vec<DeploymentPlan> = result.take(0).unwrap_or_default();
        plans
            .into_iter()
            .next()
            .ok_or_else(|| ControlCenterError::Http(http::not_found("Deployment plan not found")))
    }

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

        plan.id = id.clone();
        plan.created_at = now.clone();

        let tasks_json = serde_json::to_string(&plan.tasks).unwrap_or_else(|_| "[]".to_string());

        let name_escaped = plan.name.replace("'", "\\'");
        let desc_escaped = plan.description.replace("'", "\\'");
        let org_escaped = plan.organization.replace("'", "\\'");

        let scheduled_clause = plan
            .scheduled_at
            .as_ref()
            .map(|s| format!("'{}', ", s))
            .unwrap_or_else(|| "NONE, ".to_string());

        let query = format!(
            "CREATE deployment_plans SET id = <uuid>'{}', name = '{}', description = '{}', \
             organization = '{}', tasks = {}, status = 'pending', created_at = <datetime>'{}', \
             updated_at = <datetime>'{}', scheduled_at = {}",
            id,
            name_escaped,
            desc_escaped,
            org_escaped,
            tasks_json,
            plan.created_at,
            plan.created_at,
            scheduled_clause
        );

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

        Ok(plan)
    }

    /// Update an existing deployment plan
    pub async fn update_deployment(
        &self,
        id: &str,
        plan: DeploymentPlan,
    ) -> Result<DeploymentPlan> {
        let now = chrono::Utc::now().to_rfc3339();

        let tasks_json = serde_json::to_string(&plan.tasks).unwrap_or_else(|_| "[]".to_string());

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

        let scheduled_clause = plan
            .scheduled_at
            .as_ref()
            .map(|s| format!("'{}', ", s))
            .unwrap_or_else(|| "NONE, ".to_string());

        let query = format!(
            "UPDATE deployment_plans SET name = '{}', description = '{}', organization = '{}', \
             tasks = {}, updated_at = <datetime>'{}', scheduled_at = {} WHERE id = '{}'",
            name_escaped, desc_escaped, org_escaped, tasks_json, now, scheduled_clause, id_escaped
        );

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

        Ok(plan)
    }

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

        Ok(())
    }

    /// Create a deployment execution record
    pub async fn create_execution(&self, plan_id: &str) -> Result<DeploymentExecution> {
        let id = Uuid::new_v4().to_string();
        let now = chrono::Utc::now().to_rfc3339();
        let plan_id_escaped = plan_id.replace("'", "\\'");

        let query = format!(
            "CREATE deployment_executions SET id = <uuid>'{}', plan_id = <uuid>'{}', status = \
             'pending', progress_percentage = 0, completed_tasks = 0, total_tasks = 0, created_at \
             = <datetime>'{}'",
            id, plan_id_escaped, now
        );

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

        Ok(DeploymentExecution {
            id,
            plan_id: plan_id.to_string(),
            workflow_id: None,
            status: "pending".to_string(),
            progress_percentage: 0,
            completed_tasks: 0,
            total_tasks: 0,
            started_at: None,
            completed_at: None,
        })
    }

    /// Get deployment execution status
    pub async fn get_execution(&self, plan_id: &str) -> Result<DeploymentExecution> {
        let plan_id_escaped = plan_id.replace("'", "\\'");
        let mut result = self
            .db
            .db
            .query(format!(
                "SELECT * FROM deployment_executions WHERE plan_id = '{}' ORDER BY created_at \
                 DESC LIMIT 1",
                plan_id_escaped
            ))
            .await
            .context("Failed to get execution")?;

        let executions: Vec<DeploymentExecution> = result.take(0).unwrap_or_default();
        executions.into_iter().next().ok_or_else(|| {
            ControlCenterError::Http(http::not_found("Deployment execution not found"))
        })
    }

    /// Update execution status
    pub async fn update_execution_status(
        &self,
        execution_id: &str,
        status: &str,
        progress: u32,
        completed_tasks: u32,
        total_tasks: u32,
        workflow_id: Option<String>,
    ) -> Result<()> {
        let now = chrono::Utc::now().to_rfc3339();
        let execution_id_escaped = execution_id.replace("'", "\\'");

        let started_clause = if status == "running" {
            format!("started_at = <datetime>'{}', ", now)
        } else {
            String::new()
        };

        let completed_clause = if status == "completed" || status == "failed" {
            format!("completed_at = <datetime>'{}', ", now)
        } else {
            String::new()
        };

        let workflow_clause = workflow_id
            .map(|w| format!("workflow_id = '{}', ", w.replace("'", "\\'")))
            .unwrap_or_default();

        let query = format!(
            "UPDATE deployment_executions SET {}{}{}status = '{}', progress_percentage = {}, \
             completed_tasks = {}, total_tasks = {} WHERE id = '{}'",
            started_clause,
            completed_clause,
            workflow_clause,
            status,
            progress,
            completed_tasks,
            total_tasks,
            execution_id_escaped
        );

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

        Ok(())
    }
}

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

    #[test]
    fn test_deployment_plan_creation() {
        let plan = DeploymentPlan {
            id: Uuid::new_v4().to_string(),
            name: "Test Deployment".to_string(),
            description: "A test deployment".to_string(),
            organization: "default".to_string(),
            tasks: vec![],
            created_at: chrono::Utc::now().to_rfc3339(),
            scheduled_at: None,
        };

        assert_eq!(plan.name, "Test Deployment");
        assert_eq!(plan.tasks.len(), 0);
    }

    #[test]
    fn test_task_status_display() {
        assert_eq!(TaskStatus::Pending.to_string(), "pending");
        assert_eq!(TaskStatus::Running.to_string(), "running");
        assert_eq!(TaskStatus::Completed.to_string(), "completed");
        assert_eq!(TaskStatus::Failed.to_string(), "failed");
    }
}
