//! Metadata Parser
//!
//! Parses JSON output from `nickel query` into NickelSchemaIR.
//!
//! Handles extraction of metadata from Nickel schemas including:
//! - Type contracts and annotations
//! - Documentation (| doc)
//! - Default values (| default)
//! - Optional fields (| optional)
//! - Nested record structures
//! - Fragment markers from `# @fragment: name` comments

use super::schema_ir::{NickelFieldIR, NickelSchemaIR, NickelType};
use crate::error::ErrorWrapper;
use crate::Result;
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;

/// Parser for Nickel metadata JSON from `nickel query` output
pub struct MetadataParser;

impl MetadataParser {
    /// Parse JSON metadata from nickel query into NickelSchemaIR
    ///
    /// # Arguments
    ///
    /// * `json` - JSON value from `nickel query --format json`
    ///
    /// # Returns
    ///
    /// NickelSchemaIR with parsed fields, types, and metadata
    ///
    /// # Errors
    ///
    /// Returns error if JSON structure is invalid or required fields are missing
    pub fn parse(json: Value) -> Result<NickelSchemaIR> {
        let obj = json
            .as_object()
            .ok_or_else(|| ErrorWrapper::new("Expected JSON object from nickel query"))?;

        let mut fields = Vec::new();
        Self::extract_fields(obj, Vec::new(), &mut fields)?;

        Ok(NickelSchemaIR {
            name: "generated_schema".to_string(),
            description: None,
            fields,
        })
    }

    /// Recursively extract fields from JSON object
    fn extract_fields(
        obj: &serde_json::Map<String, Value>,
        path: Vec<String>,
        fields: &mut Vec<NickelFieldIR>,
    ) -> Result<()> {
        for (key, value) in obj {
            let mut field_path = path.clone();
            field_path.push(key.clone());

            // Check if this is a field definition (has metadata keys) or a nested record
            if let Value::Object(nested_obj) = value {
                // If the object has field metadata keys (type, doc, default, etc.),
                // treat it as a field definition, not a nested structure
                let has_field_metadata = nested_obj.contains_key("type")
                    || nested_obj.contains_key("doc")
                    || nested_obj.contains_key("default")
                    || nested_obj.contains_key("optional")
                    || nested_obj.contains_key("contract");

                if has_field_metadata {
                    // This is a field definition - parse it
                    let field = Self::parse_field_value(key, value, field_path)?;
                    fields.push(field);
                } else {
                    // This is a nested record - recurse into it
                    Self::extract_fields(nested_obj, field_path, fields)?;
                }
            } else {
                // Non-object values are parsed as fields
                let field = Self::parse_field_value(key, value, field_path)?;
                fields.push(field);
            }
        }

        Ok(())
    }

    /// Parse a single field from its JSON value
    fn parse_field_value(_name: &str, value: &Value, path: Vec<String>) -> Result<NickelFieldIR> {
        // Use hyphen separator for collision-free flat_name
        let flat_name = path.join("-");
        // Generate alias using heuristics
        let alias = super::alias_generator::AliasGenerator::generate(&path);

        // Extract metadata from the value
        let doc = Self::extract_doc(value);
        let default = Self::extract_default(value);
        let optional = Self::extract_optional(value);
        let contract = Self::extract_contract(value);
        let nickel_type = Self::infer_type(value, &path);

        // Detect array-of-records pattern: Array(Record(...))
        let (is_array_of_records, array_element_fields) = match &nickel_type {
            NickelType::Array(elem_type) => {
                // Check if the element type is a Record
                if let NickelType::Record(fields) = elem_type.as_ref() {
                    // This is an array of records - extract element field definitions
                    (true, Some(fields.clone()))
                } else {
                    (false, None)
                }
            }
            _ => (false, None),
        };

        Ok(NickelFieldIR {
            path,
            flat_name,
            alias,
            nickel_type,
            doc,
            default,
            optional,
            contract,
            contract_call: None, // Will be assigned from contract_parser if present
            group: None,         // Will be assigned during form generation
            fragment_marker: None, // Will be assigned from source comments if present
            is_array_of_records,
            array_element_fields,
            encryption_metadata: None,
        })
    }

    /// Extract documentation from field value
    fn extract_doc(value: &Value) -> Option<String> {
        if let Value::Object(obj) = value {
            // Look for doc metadata
            if let Some(Value::String(doc)) = obj.get("doc") {
                return Some(doc.clone());
            }
            // Check metadata field
            if let Some(Value::Object(meta_obj)) = obj.get("metadata") {
                if let Some(Value::String(doc)) = meta_obj.get("doc") {
                    return Some(doc.clone());
                }
            }
        }
        None
    }

    /// Extract default value from field value
    fn extract_default(value: &Value) -> Option<serde_json::Value> {
        if let Value::Object(obj) = value {
            // Look for default value
            if let Some(default) = obj.get("default") {
                return Some(default.clone());
            }
            // Check metadata field
            if let Some(Value::Object(meta_obj)) = obj.get("metadata") {
                if let Some(default) = meta_obj.get("default") {
                    return Some(default.clone());
                }
            }
        }
        None
    }

    /// Extract optional flag from field value
    fn extract_optional(value: &Value) -> bool {
        if let Value::Object(obj) = value {
            // Check optional flag
            if let Some(Value::Bool(opt)) = obj.get("optional") {
                return *opt;
            }
            // Check metadata field
            if let Some(Value::Object(meta_obj)) = obj.get("metadata") {
                if let Some(Value::Bool(opt)) = meta_obj.get("optional") {
                    return *opt;
                }
            }
        }
        false
    }

    /// Extract Nickel contract/predicate from field value
    fn extract_contract(value: &Value) -> Option<String> {
        if let Value::Object(obj) = value {
            // Look for contract annotation
            if let Some(Value::String(contract)) = obj.get("contract") {
                return Some(contract.clone());
            }
            // Check type field for contract info
            if let Some(Value::String(type_str)) = obj.get("type") {
                if type_str.contains("|") {
                    return Some(type_str.clone());
                }
            }
        }
        None
    }

    /// Infer Nickel type from JSON value
    fn infer_type(value: &Value, _path: &[String]) -> NickelType {
        match value {
            Value::Null => NickelType::Custom("unknown".to_string()),
            Value::Bool(_) => NickelType::Bool,
            Value::Number(_) => NickelType::Number,
            Value::String(_) => NickelType::String,
            Value::Array(arr) => {
                // Infer array element type from first element
                if let Some(elem) = arr.first() {
                    let elem_type = Self::infer_type(elem, &[]);
                    NickelType::Array(Box::new(elem_type))
                } else {
                    NickelType::Array(Box::new(NickelType::Custom("unknown".to_string())))
                }
            }
            Value::Object(obj) => {
                // For nested objects, extract fields
                let mut nested_fields = Vec::new();
                let path_copy = _path.to_vec();
                drop(Self::extract_fields(obj, path_copy, &mut nested_fields));
                NickelType::Record(nested_fields)
            }
        }
    }

    /// Extract fragment markers from Nickel source file
    ///
    /// Looks for comments like `# @fragment: section_name` before field definitions.
    /// Returns a map of field_name -> fragment_marker.
    pub fn extract_fragment_markers_from_source(
        source_path: &Path,
    ) -> Result<HashMap<String, String>> {
        let source_code = std::fs::read_to_string(source_path)
            .map_err(|e| ErrorWrapper::new(format!("Failed to read source file: {}", e)))?;

        let mut markers = HashMap::new();
        let mut current_fragment: Option<String> = None;

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

            // Check for fragment marker comment
            if let Some(fragment_name) = Self::extract_fragment_marker_from_comment(trimmed) {
                current_fragment = Some(fragment_name);
            }
            // Skip comments and empty lines
            else if trimmed.is_empty() || trimmed.starts_with('#') {
                continue;
            }
            // Parse field name from line (simple heuristic: identifier before | or :)
            else if let Some(field_name) = Self::extract_field_name(trimmed) {
                if let Some(fragment) = &current_fragment {
                    markers.insert(field_name, fragment.clone());
                }
            }
        }

        Ok(markers)
    }

    /// Extract fragment marker from a comment line
    fn extract_fragment_marker_from_comment(line: &str) -> Option<String> {
        if line.starts_with('#') && line.contains("@fragment:") {
            let start = line.find("@fragment:")?;
            let after_marker = &line[start + 10..].trim();
            let fragment_name = after_marker.split_whitespace().next()?.to_string();

            if !fragment_name.is_empty() {
                return Some(fragment_name);
            }
        }
        None
    }

    /// Extract field name from a line (e.g., "field_name | doc" -> "field_name")
    fn extract_field_name(line: &str) -> Option<String> {
        // Simple heuristic: take the first identifier
        let name = line
            .split_whitespace()
            .next()?
            .split('|')
            .next()?
            .split(':')
            .next()?;

        if !name.is_empty() && !name.ends_with(',') {
            return Some(name.to_string());
        }
        None
    }

    /// Apply fragment markers to parsed schema fields
    pub fn apply_fragment_markers(schema: &mut NickelSchemaIR, markers: &HashMap<String, String>) {
        for field in &mut schema.fields {
            if let Some(marker) = markers.get(&field.flat_name) {
                field.fragment_marker = Some(marker.clone());
            }
        }
    }
}

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

    #[test]
    fn test_parse_simple_field() {
        let json = json!({
            "user": {
                "name": "Alice",
                "age": 30
            }
        });

        let result = MetadataParser::parse(json);
        assert!(result.is_ok());
        let schema = result.unwrap();
        // Flattened: user-name and user-age (hyphen separator for collision-free names)
        assert_eq!(schema.fields.len(), 2);
        let flat_names: Vec<_> = schema.fields.iter().map(|f| f.flat_name.as_str()).collect();
        assert!(flat_names.contains(&"user-name"));
        assert!(flat_names.contains(&"user-age"));
    }

    #[test]
    fn test_parse_with_optional() {
        let json = json!({
            "settings": {
                "email": "test@example.com"
            }
        });

        let result = MetadataParser::parse(json);
        assert!(result.is_ok());
        let schema = result.unwrap();
        // Should have settings-email field (hyphen separator)
        assert_eq!(schema.fields.len(), 1);
        assert_eq!(schema.fields[0].flat_name, "settings-email");
    }

    #[test]
    fn test_extract_doc() {
        let value = json!({
            "doc": "Test documentation"
        });
        let doc = MetadataParser::extract_doc(&value);
        assert_eq!(doc, Some("Test documentation".to_string()));
    }

    #[test]
    fn test_extract_default() {
        let value = json!({
            "default": 42
        });
        let default = MetadataParser::extract_default(&value);
        assert_eq!(default, Some(json!(42)));
    }
}
