//! Contract Parser for Nickel sources
//!
//! Extracts validator imports and contract calls from Nickel source files.
//! Enables roundtrip workflows where validators are preserved from source → form → output.

use super::schema_ir::ContractCall;
use crate::Result;
use std::collections::HashMap;

/// Parser for extracting contract metadata from Nickel source
pub struct ContractParser;

/// Result of parsing a Nickel file for contracts
#[derive(Debug, Clone)]
pub struct ParsedContracts {
    /// Imports found: map of imported name to module path
    /// Example: validators -> ../validators/environment.ncl
    pub imports: HashMap<String, String>,

    /// Contract calls found per field
    /// Example: "environment_name" -> ContractCall { module: "validators", function: "ValidEnvironmentName", ... }
    pub field_contracts: HashMap<String, ContractCall>,

    /// Raw source for template rendering
    pub source: String,
}

impl ContractParser {
    /// Parse a Nickel source file and extract contract information
    pub fn parse_source(source: &str) -> Result<ParsedContracts> {
        Ok(ParsedContracts {
            imports: Self::extract_imports(source),
            field_contracts: Self::extract_contract_calls(source),
            source: source.to_string(),
        })
    }

    /// Extract import statements from Nickel source
    /// Pattern: let NAME = import "PATH" in
    fn extract_imports(source: &str) -> HashMap<String, String> {
        let mut imports = HashMap::new();

        for line in source.lines() {
            let trimmed = line.trim();
            if trimmed.starts_with("let ") && trimmed.contains("import ") && trimmed.contains(" in")
            {
                if let Some(eq_idx) = trimmed.find('=') {
                    let name_part = trimmed[4..eq_idx].trim();
                    if let Some(quote_start) = trimmed.find('"') {
                        if let Some(quote_end) = trimmed[quote_start + 1..].find('"') {
                            let path_part = &trimmed[quote_start + 1..quote_start + 1 + quote_end];
                            imports.insert(name_part.to_string(), path_part.to_string());
                        }
                    }
                }
            }
        }

        imports
    }

    /// Extract contract calls from user_config with field name mapping
    /// Pattern: field_name = module.function args
    /// Examples: environment_name = validators.ValidPort 22
    fn extract_contract_calls(source: &str) -> HashMap<String, ContractCall> {
        let mut calls = HashMap::new();

        // Find section between "let user_config = {" and "} in"
        if let Some(config_start) = source.find("let user_config = {") {
            if let Some(config_end) = source[config_start..].find("} in") {
                let config_section = &source[config_start..config_start + config_end + 1];

                // Find all lines with = and extract field_name = module.function calls
                for line in config_section.lines() {
                    if line.contains('=') && !line.trim().starts_with('#') {
                        // Parse: field_name = validator.Function args
                        if let Some(eq_pos) = line.find('=') {
                            let left_side = line[..eq_pos].trim();
                            let right_side = line[eq_pos + 1..].trim();

                            // Extract field name
                            let field_name = if left_side.is_empty() {
                                continue;
                            } else {
                                left_side.to_string()
                            };

                            // Skip if right_side is a literal value (string, number, bool, object, array)
                            let first_char = right_side.chars().next();
                            if matches!(
                                first_char,
                                Some('"')
                                    | Some('[')
                                    | Some('{')
                                    | Some('-')
                                    | Some('0'..='9')
                                    | Some('t')
                                    | Some('f')
                                    | Some('n')
                            ) {
                                // Could be string, array, object, number, true, false, null
                                // Skip parsing as validator call
                                tracing::debug!(
                                    field = %field_name,
                                    value = %right_side,
                                    "Skipping literal field (not a validator call)"
                                );
                                continue;
                            }
                            tracing::debug!(
                                field = %field_name,
                                expression = %right_side,
                                "Parsing field with validator"
                            );

                            // Extract module.function pairs from right side
                            if let Some(dot_pos) = right_side.find('.') {
                                // Find the start of the identifier before the dot
                                let mut module_start = dot_pos;
                                while module_start > 0
                                    && Self::is_identifier_char(
                                        right_side.chars().nth(module_start - 1).unwrap(),
                                    )
                                {
                                    module_start -= 1;
                                }

                                let module = right_side[module_start..dot_pos].trim();

                                // Find the function name after the dot
                                let after_dot = &right_side[dot_pos + 1..];
                                let mut function_end = 0;
                                for ch in after_dot.chars() {
                                    if ch.is_alphanumeric() || ch == '_' {
                                        function_end += 1;
                                    } else {
                                        break;
                                    }
                                }

                                if function_end > 0 {
                                    let function = &after_dot[..function_end];
                                    let rest = &after_dot[function_end..].trim();

                                    // Extract args (everything until comma or closing brace)
                                    let args_end = rest.find([',', '}']).unwrap_or(rest.len());
                                    let args = rest[..args_end].trim();
                                    let args = if args.is_empty() {
                                        None
                                    } else {
                                        Some(args.to_string())
                                    };

                                    let expr = format!(
                                        "{}.{}{}",
                                        module,
                                        function,
                                        args.as_ref()
                                            .map(|a| format!(" {}", a))
                                            .unwrap_or_default()
                                    );

                                    let call = ContractCall {
                                        module: module.to_string(),
                                        function: function.to_string(),
                                        args,
                                        expr,
                                    };

                                    // Key by field name, not module.function
                                    calls.insert(field_name, call);
                                }
                            }
                        }
                    }
                }
            }
        }

        calls
    }

    fn is_identifier_char(ch: char) -> bool {
        ch.is_alphanumeric() || ch == '_'
    }

    /// Extract the user_config record from source (between `let user_config = { ... } in`)
    pub fn extract_user_config(source: &str) -> Option<String> {
        if let Some(start) = source.find("let user_config = {") {
            if let Some(end) = source[start..].find("} in") {
                let content = &source[start + 19..start + end + 1];
                return Some(content.to_string());
            }
        }
        None
    }

    /// Get the final expression (usually just `user_config`)
    pub fn extract_final_expr(source: &str) -> Option<String> {
        // Match the last non-comment, non-blank line before EOF
        let lines: Vec<&str> = source
            .lines()
            .filter(|line| !line.trim().is_empty() && !line.trim().starts_with('#'))
            .collect();

        lines.last().map(|&line| line.trim().to_string())
    }
}

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

    #[test]
    fn test_extract_contracts_from_file() {
        // Test with the actual content from /tmp/minimal_input.ncl
        let source = r#"let validators = import "/Users/Akasha/Development/typedialog/provisioning/validators/environment.ncl" in

let user_config = {
  env_name = validators.ValidEnvironmentName "dev",
  db_name = "tracker.db",
} in

user_config
        "#;

        let calls = ContractParser::extract_contract_calls(source);

        assert_eq!(
            calls.len(),
            1,
            "Should only extract 1 contract, but got: {:?}",
            calls.keys().collect::<Vec<_>>()
        );
        assert!(calls.contains_key("env_name"));
        assert!(!calls.contains_key("db_name"));
    }

    #[test]
    fn test_extract_contracts_ignores_literals() {
        let source = r#"
let validators = import "../validators/environment.ncl" in

let user_config = {
  env_name = validators.ValidEnvironmentName "dev",
  db_name = "tracker.db",
  enabled = true,
  count = 42,
} in

user_config
        "#;

        let calls = ContractParser::extract_contract_calls(source);

        // Should only extract env_name validator, not literals
        assert_eq!(
            calls.len(),
            1,
            "Should only extract 1 contract, but got: {:?}",
            calls.keys().collect::<Vec<_>>()
        );
        assert!(calls.contains_key("env_name"));
        assert!(!calls.contains_key("db_name"));
        assert!(!calls.contains_key("enabled"));
        assert!(!calls.contains_key("count"));
    }

    #[test]
    fn test_extract_imports() {
        let source = r#"
let validators = import "../validators/environment.ncl" in
let validators_common = import "../validators/common.ncl" in
let user_config = {} in
user_config
        "#;

        let imports = ContractParser::extract_imports(source);
        assert_eq!(
            imports.get("validators").map(|s| s.as_str()),
            Some("../validators/environment.ncl")
        );
        assert_eq!(
            imports.get("validators_common").map(|s| s.as_str()),
            Some("../validators/common.ncl")
        );
    }

    #[test]
    fn test_extract_user_config() {
        let source = r#"
let user_config = {
  name = "test",
  port = 8080,
} in
user_config
        "#;

        let config = ContractParser::extract_user_config(source);
        assert!(config.is_some());
        assert!(config.unwrap().contains("name"));
    }

    #[test]
    fn test_extract_final_expr() {
        let source = "let x = 1 in\nuser_config";
        let expr = ContractParser::extract_final_expr(source);
        assert_eq!(expr, Some("user_config".to_string()));
    }
}
