//! Mode B: Load project specification from explicit config.toml.

use crate::error::{ConfigLoadingError, Result};
use crate::models::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;

/// Loads ProjectSpec from an explicit project-spec.toml configuration file.
pub struct ConfigLoader;

/// TOML-serializable configuration spec (mirrors ProjectSpec for file I/O).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigSpec {
    /// Project name
    pub name: String,

    /// Project type
    pub project_type: String,

    /// Infrastructure configuration
    #[serde(default)]
    pub infrastructure: InfrastructureConfig,

    /// Domain features
    #[serde(default)]
    pub features: Vec<FeatureConfig>,

    /// Constraints
    #[serde(default)]
    pub constraints: Vec<ConstraintConfig>,
}

/// Infrastructure configuration in TOML format.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct InfrastructureConfig {
    /// SSH support
    #[serde(default)]
    pub ssh: bool,

    /// Database configuration
    pub database: Option<DatabaseConfig>,

    /// Cloud providers
    #[serde(default)]
    pub providers: Vec<String>,

    /// Monitoring tools
    #[serde(default)]
    pub monitoring: Vec<String>,
}

/// Database configuration in TOML format.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseConfig {
    /// Database type (sqlite, mysql, postgres)
    pub r#type: String,

    /// Is database required?
    #[serde(default = "default_true")]
    pub required: bool,
}

fn default_true() -> bool {
    true
}

/// Domain feature configuration in TOML format.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureConfig {
    /// Feature name
    pub name: String,

    /// Human-readable description
    pub description: Option<String>,

    /// Configuration fields
    #[serde(default)]
    pub fields: Vec<FieldConfig>,

    /// Feature-specific constraints
    #[serde(default)]
    pub constraints: HashMap<String, ConstraintConfigValue>,
}

/// Configuration field in TOML format.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldConfig {
    /// Field name
    pub name: String,

    /// Field type
    pub r#type: String,

    /// Human-readable prompt
    pub prompt: String,

    /// Is field required?
    #[serde(default = "default_true")]
    pub required: bool,

    /// Default value
    pub default: Option<serde_json::Value>,

    /// Help text
    pub help: Option<String>,

    /// Placeholder value
    pub placeholder: Option<String>,

    /// For select fields: available options
    #[serde(default)]
    pub options: Vec<String>,

    /// Min value or array length
    pub min: Option<u32>,

    /// Max value or array length
    pub max: Option<u32>,

    /// Item fragment for repeating groups
    pub item_fragment: Option<String>,

    /// Is field encrypted/sensitive?
    #[serde(default)]
    pub sensitive: bool,

    /// Encryption backend
    pub encryption_backend: Option<String>,

    /// Encryption config (key-value pairs)
    #[serde(default)]
    pub encryption_config: HashMap<String, String>,
}

/// Constraint configuration in TOML format.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstraintConfig {
    /// Constraint path (e.g., "feature.field")
    pub path: String,

    /// Minimum items (for arrays)
    pub min_items: Option<u32>,

    /// Maximum items (for arrays)
    pub max_items: Option<u32>,

    /// Unique constraint field
    pub unique: Option<String>,
}

/// Feature constraint value type.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ConstraintConfigValue {
    /// Simple string constraint
    String(String),
    /// Numeric constraint
    Number(u32),
    /// Constraint object
    Object(HashMap<String, serde_json::Value>),
}

impl ConfigLoader {
    /// Load ProjectSpec from config file.
    pub fn load(config_path: impl AsRef<Path>) -> Result<ProjectSpec> {
        let config_path = config_path.as_ref();

        if !config_path.exists() {
            return Err(ConfigLoadingError {
                config_path: config_path.to_path_buf(),
                reason: "File does not exist".to_string(),
            }
            .into());
        }

        let config_content =
            std::fs::read_to_string(config_path).map_err(|e| ConfigLoadingError {
                config_path: config_path.to_path_buf(),
                reason: format!("Failed to read file: {}", e),
            })?;

        let config_spec: ConfigSpec =
            toml::from_str(&config_content).map_err(|e| ConfigLoadingError {
                config_path: config_path.to_path_buf(),
                reason: format!("Failed to parse TOML: {}", e),
            })?;

        Self::convert_to_project_spec(config_spec, config_path)
    }

    /// Convert ConfigSpec to ProjectSpec.
    fn convert_to_project_spec(config: ConfigSpec, config_path: &Path) -> Result<ProjectSpec> {
        // Parse project type
        let project_type = match config.project_type.to_lowercase().as_str() {
            "web" | "webservice" | "web-service" => ProjectType::WebService,
            "cli" | "clitool" | "cli-tool" => ProjectType::CliTool,
            "micro" | "microservice" => ProjectType::Microservice,
            "lib" | "library" => ProjectType::Library,
            _ => ProjectType::Custom,
        };

        // Convert infrastructure
        let infrastructure = Self::convert_infrastructure(&config.infrastructure)?;

        // Convert domain features
        let domain_features = Self::convert_features(&config.features)?;

        // Convert constraints
        let constraints = Self::convert_constraints(&config.constraints)?;

        let mut spec = ProjectSpec::new(config.name, project_type);
        spec.infrastructure = infrastructure;
        spec.domain_features = domain_features;
        spec.constraints = constraints;

        // Validate the spec
        spec.validate().map_err(|errors| ConfigLoadingError {
            config_path: config_path.to_path_buf(),
            reason: format!("Invalid config: {}", errors.join(", ")),
        })?;

        Ok(spec)
    }

    /// Convert infrastructure configuration.
    fn convert_infrastructure(infra: &InfrastructureConfig) -> Result<InfrastructureSpec> {
        let database = match &infra.database {
            Some(db) => Some(DatabaseSpec {
                db_type: match db.r#type.to_lowercase().as_str() {
                    "sqlite" | "sql" => DatabaseType::Sqlite,
                    "mysql" => DatabaseType::Mysql,
                    "postgres" | "postgresql" | "pg" => DatabaseType::Postgres,
                    "redis" => DatabaseType::Redis,
                    other => {
                        return Err(ConfigLoadingError {
                            config_path: std::path::PathBuf::new(),
                            reason: format!("Unknown database type: {}", other),
                        }
                        .into());
                    }
                },
                required: db.required,
            }),
            None => None,
        };

        let providers = infra
            .providers
            .iter()
            .filter_map(|p| match p.to_lowercase().as_str() {
                "lxd" => Some(CloudProvider::Lxd),
                "hetzner" => Some(CloudProvider::Hetzner),
                "aws" => Some(CloudProvider::Aws),
                "gcp" | "google" => Some(CloudProvider::Gcp),
                "azure" => Some(CloudProvider::Azure),
                _ => None,
            })
            .collect::<Vec<_>>();

        let monitoring = infra
            .monitoring
            .iter()
            .filter_map(|m| match m.to_lowercase().as_str() {
                "prometheus" => Some(MonitoringType::Prometheus),
                "grafana" => Some(MonitoringType::Grafana),
                "cloudwatch" => Some(MonitoringType::CloudWatch),
                "stackdriver" => Some(MonitoringType::StackDriver),
                _ => None,
            })
            .collect::<Vec<_>>();

        Ok(InfrastructureSpec {
            ssh: infra.ssh,
            database,
            providers,
            monitoring,
        })
    }

    /// Convert domain features.
    fn convert_features(features: &[FeatureConfig]) -> Result<Vec<DomainFeature>> {
        features
            .iter()
            .map(|f| {
                let fields = f
                    .fields
                    .iter()
                    .map(Self::convert_field)
                    .collect::<Result<Vec<_>>>()?;

                let mut feature = DomainFeature::new(f.name.clone());
                feature.description = f.description.clone();
                feature.fields = fields;

                Ok(feature)
            })
            .collect()
    }

    /// Convert a configuration field.
    fn convert_field(field: &FieldConfig) -> Result<ConfigField> {
        let field_type = match field.r#type.to_lowercase().as_str() {
            "text" => FieldType::Text,
            "number" | "num" => FieldType::Number,
            "password" | "secret" => FieldType::Password,
            "confirm" | "checkbox" => FieldType::Confirm,
            "select" => FieldType::Select,
            "multiselect" | "multi-select" => FieldType::MultiSelect,
            "editor" => FieldType::Editor,
            "date" => FieldType::Date,
            "repeating" | "repeating-group" => FieldType::RepeatingGroup,
            other => {
                return Err(ConfigLoadingError {
                    config_path: std::path::PathBuf::new(),
                    reason: format!("Unknown field type: {}", other),
                }
                .into());
            }
        };

        let mut encryption_config = HashMap::new();
        for (k, v) in &field.encryption_config {
            encryption_config.insert(k.clone(), v.clone());
        }

        Ok(ConfigField {
            name: field.name.clone(),
            field_type,
            prompt: field.prompt.clone(),
            required: field.required,
            default: field.default.clone(),
            help: field.help.clone(),
            placeholder: field.placeholder.clone(),
            options: field.options.clone(),
            min: field.min,
            max: field.max,
            item_fragment: field.item_fragment.clone(),
            sensitive: field.sensitive,
            encryption_backend: field.encryption_backend.clone(),
            encryption_config: if encryption_config.is_empty() {
                None
            } else {
                Some(encryption_config)
            },
        })
    }

    /// Convert constraints.
    fn convert_constraints(constraints: &[ConstraintConfig]) -> Result<Vec<Constraint>> {
        constraints
            .iter()
            .map(|c| {
                let mut constraint = Constraint::new(c.path.clone());

                if let Some(min) = c.min_items {
                    constraint = constraint.with_min_items(min);
                }
                if let Some(max) = c.max_items {
                    constraint = constraint.with_max_items(max);
                }
                if let Some(unique) = &c.unique {
                    constraint = constraint.with_unique(unique.clone());
                }

                Ok(constraint)
            })
            .collect()
    }
}
