//! Provisioning integration for Kubernetes deployment

use crate::error::Result;
use crate::LifecycleError;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Provisioning configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvisioningConfig {
    /// Enable provisioning
    pub enabled: bool,
    /// Workspace name
    pub workspace_name: String,
    /// Workspace path
    pub workspace_path: PathBuf,
    /// Cluster type (k3s, rke2, kind, aws, gcp, azure)
    pub cluster_type: String,
    /// Minimum nodes
    pub min_nodes: usize,
    /// Maximum nodes
    pub max_nodes: usize,
    /// Services
    pub services: Vec<ServiceConfig>,
    /// Storage configuration
    pub storage: StorageConfig,
}

impl Default for ProvisioningConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            workspace_name: "project-wrksp".to_string(),
            workspace_path: PathBuf::from("provisioning/project-wrksp"),
            cluster_type: "k3s".to_string(),
            min_nodes: 3,
            max_nodes: 10,
            services: vec![],
            storage: StorageConfig::default(),
        }
    }
}

/// Service configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceConfig {
    /// Service name
    pub name: String,
    /// Service image
    pub image: String,
    /// Service replicas
    pub replicas: usize,
    /// Service port
    pub port: u16,
    /// Memory limit (Mi)
    pub memory_limit: Option<usize>,
    /// CPU limit (m)
    pub cpu_limit: Option<usize>,
}

impl ServiceConfig {
    /// Create new service config
    pub fn new(name: impl Into<String>, image: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            image: image.into(),
            replicas: 1,
            port: 8080,
            memory_limit: None,
            cpu_limit: None,
        }
    }

    /// Set replicas
    pub fn with_replicas(mut self, replicas: usize) -> Self {
        self.replicas = replicas;
        self
    }

    /// Set port
    pub fn with_port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }

    /// Set memory limit
    pub fn with_memory_limit(mut self, memory_mi: usize) -> Self {
        self.memory_limit = Some(memory_mi);
        self
    }

    /// Set CPU limit
    pub fn with_cpu_limit(mut self, cpu_m: usize) -> Self {
        self.cpu_limit = Some(cpu_m);
        self
    }
}

/// Storage configuration
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct StorageConfig {
    /// Database replicas (SurrealDB)
    pub db_replicas: usize,
    /// Database storage size
    pub db_storage: String,
    /// Cache replicas (Redis)
    pub cache_replicas: usize,
    /// Cache storage size
    pub cache_storage: String,
}

/// Provisioning manager
#[derive(Debug)]
pub struct ProvisioningManager {
    /// Provisioning configuration
    pub config: ProvisioningConfig,
}

impl ProvisioningManager {
    /// Create a new provisioning manager
    pub fn new(config: ProvisioningConfig) -> Self {
        Self { config }
    }

    /// Generate workspace.toml content
    pub fn generate_workspace_toml(&self) -> Result<String> {
        let content = format!(
            r#"[workspace]
name = "{}"
cluster_type = "{}"
min_nodes = {}
max_nodes = {}

[storage]
db_replicas = {}
db_storage = "{}"
cache_replicas = {}
cache_storage = "{}"

{}"#,
            self.config.workspace_name,
            self.config.cluster_type,
            self.config.min_nodes,
            self.config.max_nodes,
            self.config.storage.db_replicas,
            self.config.storage.db_storage,
            self.config.storage.cache_replicas,
            self.config.storage.cache_storage,
            self.generate_services_toml()
        );

        Ok(content)
    }

    /// Generate services section for workspace.toml
    fn generate_services_toml(&self) -> String {
        if self.config.services.is_empty() {
            return String::new();
        }

        let mut services = String::from("[services]\n");
        for svc in &self.config.services {
            services.push_str(&format!(
                r#"{}.name = "{}"
{}.image = "{}"
{}.replicas = {}
{}.port = {}
"#,
                svc.name, svc.name, svc.name, svc.image, svc.name, svc.replicas, svc.name, svc.port
            ));
        }

        services
    }

    /// Generate KCL cluster schema
    pub fn generate_cluster_kcl(&self) -> Result<String> {
        let content = format!(
            r#"#!/usr/bin/env kcl
# Cluster configuration for {}

cluster = {{
    name: "{}",
    type: "{}",
    min_nodes: {},
    max_nodes: {},

    storage: {{
        db: {{
            replicas: {},
            storage: "{}"
        }},
        cache: {{
            replicas: {},
            storage: "{}"
        }}
    }}
}}
"#,
            self.config.workspace_name,
            self.config.workspace_name,
            self.config.cluster_type,
            self.config.min_nodes,
            self.config.max_nodes,
            self.config.storage.db_replicas,
            self.config.storage.db_storage,
            self.config.storage.cache_replicas,
            self.config.storage.cache_storage
        );

        Ok(content)
    }

    /// Generate service manifest
    pub fn generate_service_manifest(&self, service: &ServiceConfig) -> Result<String> {
        let content = format!(
            r#"apiVersion: apps/v1
kind: Deployment
metadata:
  name: {}
spec:
  replicas: {}
  selector:
    matchLabels:
      app: {}
  template:
    metadata:
      labels:
        app: {}
    spec:
      containers:
      - name: {}
        image: {}
        ports:
        - containerPort: {}
{}
"#,
            service.name,
            service.replicas,
            service.name,
            service.name,
            service.name,
            service.image,
            service.port,
            self.generate_resources(service)
        );

        Ok(content)
    }

    /// Generate resources section for manifest
    fn generate_resources(&self, service: &ServiceConfig) -> String {
        if service.memory_limit.is_none() && service.cpu_limit.is_none() {
            return String::new();
        }

        let mut resources = String::from("        resources:\n");

        if let Some(memory) = service.memory_limit {
            resources.push_str(&format!(
                "          limits:\n            memory: \"{}Mi\"\n",
                memory
            ));
        }

        if let Some(cpu) = service.cpu_limit {
            resources.push_str(&format!(
                "          limits:\n            cpu: \"{}m\"\n",
                cpu
            ));
        }

        resources
    }

    /// Write workspace.toml to file
    pub async fn write_workspace_toml(&self) -> Result<()> {
        let workspace_toml_path = self.config.workspace_path.join("workspace.toml");

        // Create directory if it doesn't exist
        tokio::fs::create_dir_all(&self.config.workspace_path)
            .await
            .map_err(|e| {
                LifecycleError::provisioning(format!("Failed to create directory: {}", e))
            })?;

        let content = self.generate_workspace_toml()?;
        tokio::fs::write(&workspace_toml_path, content)
            .await
            .map_err(|e| {
                LifecycleError::provisioning(format!("Failed to write workspace.toml: {}", e))
            })?;

        Ok(())
    }

    /// Write KCL schemas
    pub async fn write_kcl_schemas(&self) -> Result<()> {
        let kcl_dir = self.config.workspace_path.join("kcl");

        // Create kcl directory
        tokio::fs::create_dir_all(&kcl_dir).await.map_err(|e| {
            LifecycleError::provisioning(format!("Failed to create kcl directory: {}", e))
        })?;

        // Write cluster.k
        let cluster_kcl = self.generate_cluster_kcl()?;
        let cluster_path = kcl_dir.join("cluster.k");
        tokio::fs::write(&cluster_path, cluster_kcl)
            .await
            .map_err(|e| {
                LifecycleError::provisioning(format!("Failed to write cluster.k: {}", e))
            })?;

        Ok(())
    }

    /// Write service manifests
    pub async fn write_service_manifests(&self) -> Result<()> {
        let k8s_dir = self.config.workspace_path.join("k8s");

        // Create k8s directory
        tokio::fs::create_dir_all(&k8s_dir).await.map_err(|e| {
            LifecycleError::provisioning(format!("Failed to create k8s directory: {}", e))
        })?;

        // Write manifests for each service
        for service in &self.config.services {
            let manifest = self.generate_service_manifest(service)?;
            let manifest_path = k8s_dir.join(format!("{}-deployment.yaml", service.name));
            tokio::fs::write(&manifest_path, manifest)
                .await
                .map_err(|e| {
                    LifecycleError::provisioning(format!(
                        "Failed to write {}-deployment.yaml: {}",
                        service.name, e
                    ))
                })?;
        }

        Ok(())
    }

    /// Initialize full provisioning workspace
    pub async fn initialize_workspace(&self) -> Result<()> {
        tracing::info!(
            "Initializing provisioning workspace at {:?}",
            self.config.workspace_path
        );

        self.write_workspace_toml().await?;
        self.write_kcl_schemas().await?;
        self.write_service_manifests().await?;

        tracing::info!("Provisioning workspace initialized successfully");
        Ok(())
    }
}

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

    #[test]
    fn test_provisioning_config_default() {
        let config = ProvisioningConfig::default();
        assert!(!config.enabled);
        assert_eq!(config.cluster_type, "k3s");
    }

    #[test]
    fn test_service_config_builder() {
        let service = ServiceConfig::new("api", "myapp/api:latest")
            .with_replicas(3)
            .with_port(8080)
            .with_memory_limit(512);

        assert_eq!(service.name, "api");
        assert_eq!(service.replicas, 3);
        assert_eq!(service.port, 8080);
        assert_eq!(service.memory_limit, Some(512));
    }

    #[test]
    fn test_provisioning_manager_creation() {
        let config = ProvisioningConfig::default();
        let manager = ProvisioningManager::new(config);
        assert!(!manager.config.enabled);
    }

    #[test]
    fn test_workspace_toml_generation() {
        let config = ProvisioningConfig::default();
        let manager = ProvisioningManager::new(config);
        let toml = manager.generate_workspace_toml().unwrap();
        assert!(toml.contains("[workspace]"));
        assert!(toml.contains("cluster_type"));
    }

    #[test]
    fn test_kcl_generation() {
        let config = ProvisioningConfig::default();
        let manager = ProvisioningManager::new(config);
        let kcl = manager.generate_cluster_kcl().unwrap();
        assert!(kcl.contains("cluster = {"));
        assert!(kcl.contains("type:"));
    }

    #[test]
    fn test_service_manifest_generation() {
        let service = ServiceConfig::new("api", "myapp/api:latest")
            .with_replicas(3)
            .with_port(8080)
            .with_memory_limit(512);

        let config = ProvisioningConfig::default();
        let manager = ProvisioningManager::new(config);
        let manifest = manager.generate_service_manifest(&service).unwrap();

        assert!(manifest.contains("kind: Deployment"));
        assert!(manifest.contains("replicas: 3"));
        assert!(manifest.contains("memory: \"512Mi\""));
    }
}
