//! Central data model for provisioning generation.
//!
//! ProjectSpec is the normalized intermediate representation that all input modes
//! (Cargo, Config, Wizard, Nickel) converge to. It defines what gets generated.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Complete project specification for provisioning generation.
///
/// This is the single source of truth that all input modes normalize to.
/// All generation happens from this spec.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectSpec {
    /// Project name (lowercase, dashes allowed, no leading numbers)
    pub name: String,

    /// Project type (web-service, cli-tool, microservice, library)
    pub project_type: ProjectType,

    /// Infrastructure requirements (SSH, database, providers, monitoring)
    pub infrastructure: InfrastructureSpec,

    /// Domain-specific features and their configuration fields
    pub domain_features: Vec<DomainFeature>,

    /// Validation constraints (array sizes, uniqueness rules, etc.)
    pub constraints: Vec<Constraint>,

    /// Infrastructure-as-Code template configuration
    #[serde(default)]
    pub iac_templates: IacTemplateSpec,

    /// Script generation configuration
    #[serde(default)]
    pub scripts: ScriptSpec,

    /// Documentation generation configuration
    #[serde(default)]
    pub docs: DocsSpec,

    /// Supported locales for i18n
    #[serde(default)]
    pub locales: Vec<LocaleSpec>,
}

impl ProjectSpec {
    /// Create a new ProjectSpec with minimal required fields.
    pub fn new(name: String, project_type: ProjectType) -> Self {
        Self {
            name,
            project_type,
            infrastructure: InfrastructureSpec::default(),
            domain_features: Vec::new(),
            constraints: Vec::new(),
            iac_templates: IacTemplateSpec::default(),
            scripts: ScriptSpec::default(),
            docs: DocsSpec::default(),
            locales: Vec::new(),
        }
    }

    /// Validate the spec for completeness and consistency.
    pub fn validate(&self) -> Result<(), Vec<String>> {
        let mut errors = Vec::new();

        // Project name validation
        if self.name.is_empty() {
            errors.push("Project name cannot be empty".to_string());
        } else if !self.name.chars().next().unwrap().is_lowercase() {
            errors.push("Project name must start with lowercase letter".to_string());
        }

        // Domain features validation
        if self.domain_features.is_empty() {
            errors.push("At least one domain feature must be defined".to_string());
        }

        for feature in &self.domain_features {
            if let Err(feature_errors) = feature.validate() {
                errors.extend(feature_errors);
            }
        }

        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }
}

impl std::fmt::Display for ProjectSpec {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Project {} ({:?}): {} features, {} constraints",
            self.name,
            self.project_type,
            self.domain_features.len(),
            self.constraints.len()
        )
    }
}

/// Project type classification.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProjectType {
    /// Web service (HTTP API, REST)
    WebService,
    /// Command-line tool
    CliTool,
    /// Microservice (containerized, distributed)
    Microservice,
    /// Shared library
    Library,
    /// Custom type (user-specified)
    Custom,
}

impl std::fmt::Display for ProjectType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::WebService => write!(f, "web-service"),
            Self::CliTool => write!(f, "cli-tool"),
            Self::Microservice => write!(f, "microservice"),
            Self::Library => write!(f, "library"),
            Self::Custom => write!(f, "custom"),
        }
    }
}

/// Infrastructure requirements.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct InfrastructureSpec {
    /// SSH/remote access required
    pub ssh: bool,

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

    /// Cloud providers to support
    pub providers: Vec<CloudProvider>,

    /// Optional monitoring features
    pub monitoring: Vec<MonitoringType>,
}

/// Database configuration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DatabaseSpec {
    pub db_type: DatabaseType,
    pub required: bool,
}

/// Supported database types.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DatabaseType {
    Sqlite,
    Mysql,
    Postgres,
    Redis,
}

impl std::fmt::Display for DatabaseType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Sqlite => write!(f, "sqlite"),
            Self::Mysql => write!(f, "mysql"),
            Self::Postgres => write!(f, "postgres"),
            Self::Redis => write!(f, "redis"),
        }
    }
}

/// Cloud providers.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CloudProvider {
    Lxd,
    Hetzner,
    Aws,
    Gcp,
    Azure,
}

impl std::fmt::Display for CloudProvider {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Lxd => write!(f, "lxd"),
            Self::Hetzner => write!(f, "hetzner"),
            Self::Aws => write!(f, "aws"),
            Self::Gcp => write!(f, "gcp"),
            Self::Azure => write!(f, "azure"),
        }
    }
}

/// Monitoring types.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum MonitoringType {
    Prometheus,
    Grafana,
    CloudWatch,
    StackDriver,
}

impl std::fmt::Display for MonitoringType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Prometheus => write!(f, "prometheus"),
            Self::Grafana => write!(f, "grafana"),
            Self::CloudWatch => write!(f, "cloudwatch"),
            Self::StackDriver => write!(f, "stackdriver"),
        }
    }
}

/// Domain-specific feature with its configuration fields.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DomainFeature {
    /// Feature name (e.g., "http_server", "authentication", "caching")
    pub name: String,

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

    /// Configuration fields for this feature
    pub fields: Vec<ConfigField>,

    /// Constraints specific to this feature (e.g., array bounds)
    pub constraints: Option<HashMap<String, FeatureConstraint>>,

    /// Nickel schema imports for this feature
    #[serde(default)]
    pub nickel_imports: Vec<NickelImport>,

    /// Whether to use type contracts in generated schemas
    #[serde(default)]
    pub use_type_contracts: bool,

    /// Custom validators for this feature
    #[serde(default)]
    pub custom_validators: Vec<ValidatorSpec>,
}

impl DomainFeature {
    /// Create a new feature with no fields.
    pub fn new(name: String) -> Self {
        Self {
            name,
            description: None,
            fields: Vec::new(),
            constraints: None,
            nickel_imports: Vec::new(),
            use_type_contracts: false,
            custom_validators: Vec::new(),
        }
    }

    /// Add a configuration field to this feature.
    pub fn with_field(mut self, field: ConfigField) -> Self {
        self.fields.push(field);
        self
    }

    /// Validate the feature for completeness.
    pub fn validate(&self) -> Result<(), Vec<String>> {
        let mut errors = Vec::new();

        if self.name.is_empty() {
            errors.push("Feature name cannot be empty".to_string());
        }

        for field in &self.fields {
            if let Err(field_errors) = field.validate() {
                errors.extend(
                    field_errors
                        .iter()
                        .map(|e| format!("Feature '{}': {}", self.name, e)),
                );
            }
        }

        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }
}

impl std::fmt::Display for DomainFeature {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Feature: {} ({} fields)", self.name, self.fields.len())
    }
}

/// Configuration field definition.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigField {
    /// Field name (matches Nickel path element)
    pub name: String,

    /// Field type (text, number, select, etc.)
    pub field_type: FieldType,

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

    /// Whether field is required
    pub required: bool,

    /// Default value (as JSON)
    pub default: Option<serde_json::Value>,

    /// Help text for the user
    pub help: Option<String>,

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

    /// For select/multi_select: available options
    pub options: Vec<String>,

    /// Min value (for numbers/arrays)
    pub min: Option<u32>,

    /// Max value (for numbers/arrays)
    pub max: Option<u32>,

    /// For repeating groups: item fragment path
    pub item_fragment: Option<String>,

    /// Is this field encrypted?
    pub sensitive: bool,

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

    /// Encryption config if sensitive
    pub encryption_config: Option<HashMap<String, String>>,
}

impl ConfigField {
    /// Create a new configuration field.
    pub fn new(name: String, field_type: FieldType, prompt: String) -> Self {
        Self {
            name,
            field_type,
            prompt,
            required: true,
            default: None,
            help: None,
            placeholder: None,
            options: Vec::new(),
            min: None,
            max: None,
            item_fragment: None,
            sensitive: false,
            encryption_backend: None,
            encryption_config: None,
        }
    }

    /// Mark field as optional.
    pub fn optional(mut self) -> Self {
        self.required = false;
        self
    }

    /// Set a default value.
    pub fn with_default(mut self, default: serde_json::Value) -> Self {
        self.default = Some(default);
        self
    }

    /// Set help text.
    pub fn with_help(mut self, help: impl Into<String>) -> Self {
        self.help = Some(help.into());
        self
    }

    /// Mark as sensitive (encrypted).
    pub fn sensitive(mut self, backend: impl Into<String>) -> Self {
        self.sensitive = true;
        self.encryption_backend = Some(backend.into());
        self
    }

    /// Validate the field.
    pub fn validate(&self) -> Result<(), Vec<String>> {
        let mut errors = Vec::new();

        if self.name.is_empty() {
            errors.push("Field name cannot be empty".to_string());
        }

        if self.prompt.is_empty() {
            errors.push(format!("Field '{}': prompt cannot be empty", self.name));
        }

        // Validate min/max constraints
        if let (Some(min), Some(max)) = (self.min, self.max) {
            if min > max {
                errors.push(format!(
                    "Field '{}': min ({}) > max ({})",
                    self.name, min, max
                ));
            }
        }

        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }
}

impl std::fmt::Display for ConfigField {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}: {:?}", self.name, self.field_type)
    }
}

/// Field type for form UI rendering and JSON serialization.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FieldType {
    Text,
    Number,
    Password,
    Confirm,
    Select,
    MultiSelect,
    Editor,
    Date,
    RepeatingGroup,
}

impl std::fmt::Display for FieldType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Text => write!(f, "text"),
            Self::Number => write!(f, "number"),
            Self::Password => write!(f, "password"),
            Self::Confirm => write!(f, "confirm"),
            Self::Select => write!(f, "select"),
            Self::MultiSelect => write!(f, "multi_select"),
            Self::Editor => write!(f, "editor"),
            Self::Date => write!(f, "date"),
            Self::RepeatingGroup => write!(f, "repeatinggroup"),
        }
    }
}

/// Constraint on a feature (array bounds, uniqueness, etc.).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Constraint {
    /// Path in TOML (e.g., "tracker.udp_trackers")
    pub path: String,

    /// Minimum array items
    pub min_items: Option<u32>,

    /// Maximum array items
    pub max_items: Option<u32>,

    /// Items must be unique
    pub unique: bool,

    /// Uniqueness key field (if unique=true)
    pub unique_key: Option<String>,
}

impl Constraint {
    /// Create a new constraint for a path.
    pub fn new(path: String) -> Self {
        Self {
            path,
            min_items: None,
            max_items: None,
            unique: false,
            unique_key: None,
        }
    }

    /// Set minimum items constraint.
    pub fn with_min_items(mut self, min: u32) -> Self {
        self.min_items = Some(min);
        self
    }

    /// Set maximum items constraint.
    pub fn with_max_items(mut self, max: u32) -> Self {
        self.max_items = Some(max);
        self
    }

    /// Set uniqueness constraint.
    pub fn with_unique(mut self, key: impl Into<String>) -> Self {
        self.unique = true;
        self.unique_key = Some(key.into());
        self
    }
}

impl std::fmt::Display for Constraint {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Constraint({})", self.path)
    }
}

/// Feature-specific constraint.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureConstraint {
    pub min_items: Option<u32>,
    pub max_items: Option<u32>,
    pub unique: bool,
}

/// Infrastructure-as-Code template configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct IacTemplateSpec {
    /// Generate Terraform/OpenTofu templates
    pub terraform_enabled: bool,
    /// Generate Ansible playbooks
    pub ansible_enabled: bool,
    /// Generate docker-compose files
    pub docker_compose_enabled: bool,
}

/// Script generation configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ScriptSpec {
    /// Generate Bash scripts
    pub bash_enabled: bool,
    /// Generate Nushell scripts
    pub nushell_enabled: bool,
    /// Enable nickel-roundtrip integration
    pub nickel_roundtrip: bool,
}

/// Documentation generation configuration.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct DocsSpec {
    /// Generate main README.md
    pub generate_readme: bool,
    /// Generate quickstart guide
    pub generate_quickstart: bool,
    /// Generate nickel-roundtrip workflow guide
    pub generate_nickel_roundtrip_guide: bool,
}

/// Locale/translation specification.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocaleSpec {
    /// Language code (e.g., "en-US", "es-ES")
    pub language_code: String,
    /// Whether this is the default locale
    pub is_default: bool,
}

impl LocaleSpec {
    /// Create a new locale specification.
    pub fn new(language_code: impl Into<String>, is_default: bool) -> Self {
        Self {
            language_code: language_code.into(),
            is_default,
        }
    }
}

/// Nickel schema import declaration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NickelImport {
    /// Import name (e.g., "database_schema")
    pub name: String,
    /// Import path relative to schema file (e.g., "./database.ncl")
    pub path: String,
}

impl NickelImport {
    /// Create a new Nickel import.
    pub fn new(name: impl Into<String>, path: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            path: path.into(),
        }
    }
}

/// Custom validator specification for advanced validation logic.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidatorSpec {
    /// Validator name (e.g., "ValidPort", "UniqueBindAddresses")
    pub name: String,
    /// Validation logic description
    pub description: Option<String>,
    /// Fields this validator applies to
    pub applies_to: Vec<String>,
    /// Validator implementation type
    pub validator_type: ValidatorType,
}

/// Type of validator implementation.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ValidatorType {
    /// Range validation (min/max)
    Range,
    /// Pattern matching (regex)
    Pattern,
    /// Custom Nickel predicate function
    CustomPredicate,
    /// Array uniqueness validator
    ArrayUniqueness,
    /// Composite validator (combines multiple validators)
    Composite,
}

impl ValidatorSpec {
    /// Create a new validator specification.
    pub fn new(name: impl Into<String>, validator_type: ValidatorType) -> Self {
        Self {
            name: name.into(),
            description: None,
            applies_to: Vec::new(),
            validator_type,
        }
    }

    /// Add field that this validator applies to.
    pub fn applies_to(mut self, field: impl Into<String>) -> Self {
        self.applies_to.push(field.into());
        self
    }

    /// Set description.
    pub fn with_description(mut self, description: impl Into<String>) -> Self {
        self.description = Some(description.into());
        self
    }
}

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

    #[test]
    fn test_project_spec_creation() {
        let spec = ProjectSpec::new("my-service".to_string(), ProjectType::WebService);
        assert_eq!(spec.name, "my-service");
        assert_eq!(spec.project_type, ProjectType::WebService);
    }

    #[test]
    fn test_project_spec_validation_fails_without_features() {
        let spec = ProjectSpec::new("my-service".to_string(), ProjectType::WebService);
        assert!(spec.validate().is_err());
    }

    #[test]
    fn test_domain_feature_creation() {
        let feature = DomainFeature::new("http_server".to_string());
        assert_eq!(feature.name, "http_server");
        assert!(feature.fields.is_empty());
    }

    #[test]
    fn test_config_field_creation() {
        let field = ConfigField::new(
            "bind_address".to_string(),
            FieldType::Text,
            "Server bind address".to_string(),
        );
        assert_eq!(field.name, "bind_address");
        assert!(field.required);
        assert!(!field.sensitive);
    }

    #[test]
    fn test_config_field_sensitive() {
        let field = ConfigField::new(
            "api_token".to_string(),
            FieldType::Password,
            "API token".to_string(),
        )
        .sensitive("age");

        assert!(field.sensitive);
        assert_eq!(field.encryption_backend, Some("age".to_string()));
    }

    #[test]
    fn test_constraint_creation() {
        let constraint = Constraint::new("tracker.udp_trackers".to_string())
            .with_min_items(1)
            .with_max_items(4)
            .with_unique("bind_address");

        assert_eq!(constraint.min_items, Some(1));
        assert_eq!(constraint.max_items, Some(4));
        assert!(constraint.unique);
    }
}
