//! Mode D: Augment existing Nickel schema to infer ProjectSpec.

use crate::error::{NickelSchemaLoadingError, Result};
use crate::models::*;
use std::path::Path;

/// Analyzes existing Nickel schema to infer ProjectSpec.
pub struct NickelSchemaLoader;

impl NickelSchemaLoader {
    /// Load ProjectSpec by analyzing an existing Nickel schema file.
    ///
    /// This mode augments existing Nickel schemas by:
    /// 1. Parsing the schema structure
    /// 2. Extracting field definitions
    /// 3. Inferring domain features from configuration records
    /// 4. Building constraints from contract definitions
    pub fn load(schema_path: impl AsRef<Path>) -> Result<ProjectSpec> {
        let schema_path = schema_path.as_ref();

        if !schema_path.exists() {
            return Err(NickelSchemaLoadingError {
                schema_path: schema_path.to_path_buf(),
                reason: "Schema file does not exist".to_string(),
            }
            .into());
        }

        // Extract the project name from the schema filename (without extension)
        let project_name = schema_path
            .file_stem()
            .and_then(|s| s.to_str())
            .map(|s| s.to_string())
            .unwrap_or_else(|| "generated-project".to_string());

        // Read the schema file
        let schema_content =
            std::fs::read_to_string(schema_path).map_err(|e| NickelSchemaLoadingError {
                schema_path: schema_path.to_path_buf(),
                reason: format!("Failed to read file: {}", e),
            })?;

        // Parse schema structure using basic pattern matching
        Self::parse_schema(&project_name, &schema_content, schema_path)
    }

    /// Parse Nickel schema content to extract field definitions.
    fn parse_schema(project_name: &str, content: &str, schema_path: &Path) -> Result<ProjectSpec> {
        // Extract features from top-level record definitions
        let domain_features = Self::extract_features(content)?;

        // Default to WebService type unless we can infer otherwise
        let project_type = if content.contains("fn handle") || content.contains("endpoint") {
            ProjectType::WebService
        } else {
            ProjectType::Library
        };

        // Try to extract infrastructure hints from documentation comments
        let infrastructure = Self::extract_infrastructure_hints(content)?;

        // Extract constraints from field definitions
        let constraints = Self::extract_constraints(content)?;

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

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

        Ok(spec)
    }

    /// Extract domain features from Nickel record definitions.
    fn extract_features(content: &str) -> Result<Vec<DomainFeature>> {
        let mut features = Vec::new();

        // Simple pattern: look for lines like `field_name | FieldType,`
        // This is a basic extraction; full Nickel parsing would be more complex
        let lines: Vec<&str> = content.lines().collect();

        let mut current_feature: Option<DomainFeature> = None;
        let mut in_record = false;

        for line in lines.iter() {
            let trimmed = line.trim();

            // Detect record start: `let RecordName = {`
            if trimmed.contains("= {") && !trimmed.starts_with("//") {
                if let Some(name) = Self::extract_record_name(trimmed) {
                    if let Some(prev_feature) = current_feature.take() {
                        features.push(prev_feature);
                    }
                    in_record = true;
                    current_feature = Some(DomainFeature::new(name));
                }
            }

            // Detect record end: `}`
            if trimmed == "}" && in_record {
                if let Some(feature) = current_feature.take() {
                    features.push(feature);
                }
                in_record = false;
            }

            // Extract field definitions while in a record
            if in_record {
                if let Some((field_name, field_type)) = Self::extract_field_definition(trimmed) {
                    let field = ConfigField::new(
                        field_name.clone(),
                        field_type,
                        format!("Configure {}", field_name),
                    );

                    if let Some(ref mut feature) = current_feature {
                        feature.fields.push(field);
                    }
                }
            }
        }

        // Push any remaining feature
        if let Some(feature) = current_feature.take() {
            features.push(feature);
        }

        // Default: at least one basic feature
        if features.is_empty() {
            features.push(DomainFeature::new("schema_config".to_string()));
        }

        Ok(features)
    }

    /// Extract record name from a definition line.
    fn extract_record_name(line: &str) -> Option<String> {
        // Pattern: `let RecordName = {`
        if let Some(start) = line.find("let ") {
            let rest = &line[start + 4..];
            if let Some(end) = rest.find(" ") {
                return Some(rest[..end].trim().to_string());
            }
        }
        None
    }

    /// Extract field definition from a Nickel line.
    fn extract_field_definition(line: &str) -> Option<(String, FieldType)> {
        // Skip comments and empty lines
        if line.is_empty() || line.starts_with("//") {
            return None;
        }

        // Pattern: `field_name | Type,` or `field_name: Type,`
        if let Some(pipe_pos) = line.find('|') {
            let field_name = line[..pipe_pos].trim().to_string();

            let field_type = if line.contains("String") {
                FieldType::Text
            } else if line.contains("Number") || line.contains("Int") {
                FieldType::Number
            } else if line.contains("Bool") {
                FieldType::Confirm
            } else if line.contains("Array") {
                FieldType::RepeatingGroup
            } else {
                FieldType::Text // Default
            };

            return Some((field_name, field_type));
        }

        None
    }

    /// Extract infrastructure hints from comments and imports.
    fn extract_infrastructure_hints(content: &str) -> Result<InfrastructureSpec> {
        let mut infrastructure = InfrastructureSpec::default();

        // Check for database hints
        if content.contains("database") || content.contains("Database") {
            infrastructure.database = Some(DatabaseSpec {
                db_type: if content.contains("postgres") {
                    DatabaseType::Postgres
                } else if content.contains("mysql") {
                    DatabaseType::Mysql
                } else {
                    DatabaseType::Sqlite
                },
                required: true,
            });
        }

        // Check for SSH hints
        if content.contains("ssh") || content.contains("SSH") || content.contains("keypair") {
            infrastructure.ssh = true;
        }

        // Check for cloud provider hints
        if content.contains("aws") || content.contains("AWS") {
            infrastructure.providers.push(CloudProvider::Aws);
        }
        if content.contains("gcp") || content.contains("google") {
            infrastructure.providers.push(CloudProvider::Gcp);
        }

        // Default to LXD if no providers specified
        if infrastructure.providers.is_empty() {
            infrastructure.providers.push(CloudProvider::Lxd);
        }

        // Check for monitoring hints
        if content.contains("prometheus") {
            infrastructure.monitoring.push(MonitoringType::Prometheus);
        }
        if content.contains("grafana") {
            infrastructure.monitoring.push(MonitoringType::Grafana);
        }

        Ok(infrastructure)
    }

    /// Extract constraints from field definitions.
    fn extract_constraints(content: &str) -> Result<Vec<Constraint>> {
        let mut constraints = Vec::new();

        // Look for patterns like `min_length = 5`, `max_length = 100`
        for line in content.lines() {
            if line.contains("min_length") {
                if let Ok(min_val) = Self::extract_numeric_constraint(line, "min_length") {
                    let constraint =
                        Constraint::new("field.min".to_string()).with_min_items(min_val);
                    constraints.push(constraint);
                }
            }

            if line.contains("max_length") {
                if let Ok(max_val) = Self::extract_numeric_constraint(line, "max_length") {
                    let constraint =
                        Constraint::new("field.max".to_string()).with_max_items(max_val);
                    constraints.push(constraint);
                }
            }
        }

        Ok(constraints)
    }

    /// Extract numeric constraint value from a line.
    fn extract_numeric_constraint(line: &str, key: &str) -> Result<u32> {
        if let Some(pos) = line.find(key) {
            let rest = &line[pos + key.len()..];
            // Look for pattern: `= number` or `: number`
            let value_part = rest.trim_start_matches('=').trim_start_matches(':').trim();
            if let Some(first_num) = value_part.split(|c: char| !c.is_numeric()).next() {
                if !first_num.is_empty() {
                    if let Ok(num) = first_num.parse::<u32>() {
                        return Ok(num);
                    }
                }
            }
        }
        Err(crate::error::NickelSchemaLoadingError {
            schema_path: std::path::PathBuf::new(),
            reason: format!("Could not extract constraint from: {}", line),
        }
        .into())
    }
}

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

    #[test]
    fn test_record_name_extraction() {
        let line = "let DatabaseConfig = {";
        assert_eq!(
            NickelSchemaLoader::extract_record_name(line),
            Some("DatabaseConfig".to_string())
        );
    }

    #[test]
    fn test_field_definition_extraction() {
        let line = "host | String,";
        let (name, field_type) = NickelSchemaLoader::extract_field_definition(line).unwrap();
        assert_eq!(name, "host");
        assert_eq!(field_type, FieldType::Text);
    }
}
