//! Nickel Roundtrip Workflow
//!
//! Orchestrates complete workflow:
//! 1. Read .ncl source file
//! 2. Parse contracts and imports
//! 3. Load form definition
//! 4. Execute form (user input)
//! 5. Render output .ncl with preserved validators
//! 6. Validate with `nickel typecheck`

use super::cli::NickelCli;
use super::contract_parser::ContractParser;
use super::template_engine::TemplateEngine;
use super::template_renderer::NickelTemplateContext;
use crate::backends::FormBackend;
use crate::form_parser;
use crate::Result;
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};

/// Configuration for a roundtrip workflow
pub struct RoundtripConfig {
    /// Path to input .ncl file
    pub input_ncl: PathBuf,

    /// Path to form definition (.toml)
    pub form_path: PathBuf,

    /// Path to output .ncl file
    pub output_ncl: PathBuf,

    /// Optional template file (.ncl.j2) for rendering output
    /// If provided, renders template instead of using contract parser
    pub template_ncl: Option<PathBuf>,

    /// Whether to validate output with nickel typecheck
    pub validate: bool,

    /// Whether to print intermediate steps
    pub verbose: bool,
}

/// Result of a roundtrip execution
pub struct RoundtripResult {
    /// Parsed contracts from input
    pub input_contracts: super::contract_parser::ParsedContracts,

    /// Form results (field values)
    pub form_results: std::collections::HashMap<String, Value>,

    /// Generated Nickel code
    pub output_nickel: String,

    /// Validation result (if enabled)
    pub validation_passed: Option<bool>,

    /// Original input Nickel code (for diff)
    pub input_nickel: String,

    /// Path to output file
    pub output_path: PathBuf,

    /// Initial values loaded from input (for change detection)
    pub initial_values: std::collections::HashMap<String, Value>,
}

impl RoundtripConfig {
    /// Create a new roundtrip configuration
    pub fn new(input_ncl: PathBuf, form_path: PathBuf, output_ncl: PathBuf) -> Self {
        Self {
            input_ncl,
            form_path,
            output_ncl,
            template_ncl: None,
            validate: true,
            verbose: false,
        }
    }

    /// Create a roundtrip configuration with an optional template file
    pub fn with_template(
        input_ncl: PathBuf,
        form_path: PathBuf,
        output_ncl: PathBuf,
        template_ncl: Option<PathBuf>,
    ) -> Self {
        Self {
            input_ncl,
            form_path,
            output_ncl,
            template_ncl,
            validate: true,
            verbose: false,
        }
    }

    /// Execute the complete roundtrip workflow with a specific backend
    pub async fn execute_with_backend(
        self,
        backend: &mut dyn FormBackend,
    ) -> Result<RoundtripResult> {
        if self.verbose {
            eprintln!("[roundtrip] Starting workflow with backend");
        }

        // Step 1: Read and parse input .ncl
        if self.verbose {
            eprintln!("[roundtrip] Reading input: {}", self.input_ncl.display());
        }

        let input_source = fs::read_to_string(&self.input_ncl).map_err(|e| {
            crate::error::ErrorWrapper::new(format!("Failed to read input file: {}", e))
        })?;

        let input_contracts = ContractParser::parse_source(&input_source)?;

        if self.verbose {
            eprintln!(
                "[roundtrip] Found {} imports",
                input_contracts.imports.len()
            );
            eprintln!(
                "[roundtrip] Found {} contract calls",
                input_contracts.field_contracts.len()
            );
        }

        // Step 2: Load defaults from input .ncl (if fields have nickel_path)
        let initial_values =
            Self::load_defaults_from_input(&self.input_ncl, &self.form_path, self.verbose)?;

        if self.verbose && !initial_values.is_empty() {
            eprintln!(
                "[roundtrip] Loaded {} default values from input",
                initial_values.len()
            );
        }

        // Step 3: Execute form to get results (with defaults pre-populated)
        if self.verbose {
            eprintln!("[roundtrip] Executing form: {}", self.form_path.display());
        }

        let initial_values_backup = initial_values.clone();
        let form_results =
            Self::execute_form_with_backend_and_defaults(&self.form_path, backend, initial_values)
                .await?;

        if self.verbose {
            eprintln!(
                "[roundtrip] Form execution produced {} fields",
                form_results.len()
            );
        }

        // Step 3: Render output with contracts or template
        if self.verbose {
            eprintln!("[roundtrip] Rendering output Nickel code");
        }

        let output_nickel = if let Some(template_path) = &self.template_ncl {
            // Use Jinja2 template rendering
            if self.verbose {
                eprintln!("[roundtrip] Using template: {}", template_path.display());
            }

            let mut engine = TemplateEngine::new();
            engine.render_file(template_path, &form_results, None)?
        } else {
            // Use contract parser method (extract validators from input and apply to values)
            let ctx = NickelTemplateContext::from_parsed_contracts(
                input_contracts.clone(),
                form_results.clone(),
            );
            ctx.render_full_nickel()
        };

        if self.verbose {
            eprintln!(
                "[roundtrip] Generated {} bytes of Nickel code",
                output_nickel.len()
            );
        }

        // Step 4: Write output
        if self.verbose {
            eprintln!("[roundtrip] Writing output: {}", self.output_ncl.display());
        }

        fs::write(&self.output_ncl, &output_nickel).map_err(|e| {
            crate::error::ErrorWrapper::new(format!("Failed to write output file: {}", e))
        })?;

        // Step 5: Validate if requested
        let validation_passed = if self.validate {
            if self.verbose {
                eprintln!("[roundtrip] Validating with nickel typecheck");
            }

            match NickelCli::typecheck(&self.output_ncl) {
                Ok(()) => {
                    if self.verbose {
                        eprintln!("[roundtrip] ✓ Validation passed");
                    }
                    Some(true)
                }
                Err(e) => {
                    if self.verbose {
                        eprintln!("[roundtrip] ✗ Validation failed: {}", e);
                    }
                    Some(false)
                }
            }
        } else {
            None
        };

        Ok(RoundtripResult {
            input_contracts,
            form_results,
            output_nickel: output_nickel.clone(),
            validation_passed,
            input_nickel: input_source,
            output_path: self.output_ncl,
            initial_values: initial_values_backup,
        })
    }

    /// Execute the complete roundtrip workflow (CLI backend)
    pub fn execute(self) -> Result<RoundtripResult> {
        if self.verbose {
            eprintln!("[roundtrip] Starting workflow");
        }

        // Step 1: Read and parse input .ncl
        if self.verbose {
            eprintln!("[roundtrip] Reading input: {}", self.input_ncl.display());
        }

        let input_source = fs::read_to_string(&self.input_ncl).map_err(|e| {
            crate::error::ErrorWrapper::new(format!("Failed to read input file: {}", e))
        })?;

        let input_contracts = ContractParser::parse_source(&input_source)?;

        if self.verbose {
            eprintln!(
                "[roundtrip] Found {} imports",
                input_contracts.imports.len()
            );
            eprintln!(
                "[roundtrip] Found {} contract calls",
                input_contracts.field_contracts.len()
            );
        }

        // Step 2: Load defaults from input .ncl (if fields have nickel_path)
        let initial_values =
            Self::load_defaults_from_input(&self.input_ncl, &self.form_path, self.verbose)?;

        if self.verbose && !initial_values.is_empty() {
            eprintln!(
                "[roundtrip] Loaded {} default values from input",
                initial_values.len()
            );
        }

        // Step 3: Execute form to get results (with defaults pre-populated)
        if self.verbose {
            eprintln!("[roundtrip] Executing form: {}", self.form_path.display());
        }

        let initial_values_backup = initial_values.clone();
        let form_results = Self::execute_form_with_defaults(&self.form_path, initial_values)?;

        if self.verbose {
            eprintln!(
                "[roundtrip] Form execution produced {} fields",
                form_results.len()
            );
        }

        // Step 3: Render output with contracts or template
        if self.verbose {
            eprintln!("[roundtrip] Rendering output Nickel code");
        }

        let output_nickel = if let Some(template_path) = &self.template_ncl {
            // Use Jinja2 template rendering
            if self.verbose {
                eprintln!("[roundtrip] Using template: {}", template_path.display());
            }

            let mut engine = TemplateEngine::new();
            engine.render_file(template_path, &form_results, None)?
        } else {
            // Use contract parser method (extract validators from input and apply to values)
            let ctx = NickelTemplateContext::from_parsed_contracts(
                input_contracts.clone(),
                form_results.clone(),
            );
            ctx.render_full_nickel()
        };

        if self.verbose {
            eprintln!(
                "[roundtrip] Generated {} bytes of Nickel code",
                output_nickel.len()
            );
        }

        // Step 4: Write output
        if self.verbose {
            eprintln!("[roundtrip] Writing output: {}", self.output_ncl.display());
        }

        fs::write(&self.output_ncl, &output_nickel).map_err(|e| {
            crate::error::ErrorWrapper::new(format!("Failed to write output file: {}", e))
        })?;

        // Step 5: Validate if requested
        let validation_passed = if self.validate {
            if self.verbose {
                eprintln!("[roundtrip] Validating with nickel typecheck");
            }

            match NickelCli::typecheck(&self.output_ncl) {
                Ok(()) => {
                    if self.verbose {
                        eprintln!("[roundtrip] ✓ Validation passed");
                    }
                    Some(true)
                }
                Err(e) => {
                    if self.verbose {
                        eprintln!("[roundtrip] ✗ Validation failed: {}", e);
                    }
                    Some(false)
                }
            }
        } else {
            None
        };

        Ok(RoundtripResult {
            input_contracts,
            form_results,
            output_nickel: output_nickel.clone(),
            validation_passed,
            input_nickel: input_source,
            output_path: self.output_ncl,
            initial_values: initial_values_backup,
        })
    }

    /// Execute a form with a specific backend and return results (with defaults)
    async fn execute_form_with_backend_and_defaults(
        form_path: &Path,
        backend: &mut dyn FormBackend,
        initial_values: HashMap<String, Value>,
    ) -> Result<HashMap<String, Value>> {
        // Read and parse form definition (.ncl or .toml by extension)
        let mut form = form_parser::load_form(form_path)?;

        // Migrate to unified elements format if needed
        form.migrate_to_elements();

        // Extract base directory for resolving relative paths
        let base_dir = form_path.parent().unwrap_or_else(|| Path::new("."));

        // execute_with_backend_i18n_with_defaults dispatches on display_mode,
        // handling both field-by-field (CLI) and complete-form (Web) correctly.
        form_parser::execute_with_backend_i18n_with_defaults(
            form,
            backend,
            None,
            base_dir,
            Some(initial_values),
        )
        .await
    }

    /// Execute a form and return results (CLI backend only, with defaults)
    fn execute_form_with_defaults(
        form_path: &Path,
        initial_values: HashMap<String, Value>,
    ) -> Result<HashMap<String, Value>> {
        use std::collections::BTreeMap;

        // Read and parse form definition (.ncl or .toml by extension)
        let mut form = form_parser::load_form(form_path)?;

        // Migrate to unified elements format
        form.migrate_to_elements();

        // Extract base directory for resolving relative paths
        let _base_dir = form_path.parent().unwrap_or_else(|| Path::new("."));

        // let mut expanded_form = form_parser::expand_includes(form, _base_dir)?;  // rescued: fragment lazy-loading pre-ADR-001
        let mut expanded_form = form;

        // Now apply initial values as defaults to the EXPANDED fields
        for element in &mut expanded_form.elements {
            if let form_parser::FormElement::Field(field) = element {
                if let Some(value) = initial_values.get(&field.name) {
                    // Set as default if field doesn't already have one
                    if field.default.is_none() {
                        field.default = Some(form_parser::value_to_string(value));
                    }
                }
            }
        }

        // Execute expanded form directly (without re-expanding includes)
        // This is a simplified version of execute_with_base_dir that doesn't call expand_includes again
        let mut results = HashMap::new();

        // Print form header
        if let Some(desc) = &expanded_form.description {
            println!("\n{}\n{}\n", expanded_form.name, desc);
        } else {
            println!("\n{}\n", expanded_form.name);
        }

        // Build ordered element map
        let mut element_map: BTreeMap<usize, form_parser::FormElement> = BTreeMap::new();
        let mut order_counter = 0;

        for element in expanded_form.elements {
            let order = match &element {
                form_parser::FormElement::Item(item) => {
                    if item.order == 0 {
                        order_counter += 1;
                        order_counter - 1
                    } else {
                        item.order
                    }
                }
                form_parser::FormElement::Field(field) => {
                    if field.order == 0 {
                        order_counter += 1;
                        order_counter - 1
                    } else {
                        field.order
                    }
                }
            };
            element_map.insert(order, element);
        }

        // Process elements in order (using CLI prompts)
        for (_, element) in element_map.iter() {
            match element {
                form_parser::FormElement::Item(item) => {
                    form_parser::render_display_item(item, &results);
                }
                form_parser::FormElement::Field(field) => {
                    // Check if field should be shown based on conditional
                    if let Some(condition) = &field.when {
                        if !form_parser::evaluate_condition(condition, &results) {
                            // Field condition not met, skip it
                            continue;
                        }
                    }

                    let value = crate::backends::cli::InquireBackend::new()
                        .execute_field_sync(field, &results)?;
                    results.insert(field.name.clone(), value.clone());
                }
            }
        }

        Ok(results)
    }

    /// Load defaults from input Nickel file using form field nickel_path
    fn load_defaults_from_input(
        input_path: &Path,
        form_path: &Path,
        verbose: bool,
    ) -> Result<HashMap<String, Value>> {
        // Export input .ncl to JSON
        let json_value = match NickelCli::export(input_path) {
            Ok(val) => val,
            Err(_) => {
                // If export fails (e.g., file is empty or has syntax errors),
                // return empty defaults rather than failing the whole roundtrip
                if verbose {
                    eprintln!(
                        "[roundtrip] Warning: Could not export input .ncl, proceeding without defaults"
                    );
                }
                return Ok(HashMap::new());
            }
        };

        // Load form to get field definitions with nickel_path (.ncl or .toml by extension)
        let mut form = form_parser::load_form(form_path)?;
        form.migrate_to_elements();

        // Extract base directory for resolving paths
        let _base_dir = form_path.parent().unwrap_or_else(|| Path::new("."));

        // let expanded_form = form_parser::expand_includes(form, _base_dir)?;  // rescued: fragment lazy-loading pre-ADR-001
        let expanded_form = form;

        // Extract field definitions that have nickel_path
        let fields_with_paths: Vec<_> = expanded_form
            .elements
            .iter()
            .filter_map(|elem| {
                if let form_parser::FormElement::Field(field) = elem {
                    if field.nickel_path.is_some() {
                        Some(field.clone())
                    } else {
                        None
                    }
                } else {
                    None
                }
            })
            .collect();

        if fields_with_paths.is_empty() {
            // No fields with nickel_path, return empty
            return Ok(HashMap::new());
        }

        // Extract values using nickel_path
        let mut defaults = HashMap::new();

        if let Value::Object(obj) = json_value {
            for field in &fields_with_paths {
                if let Some(nickel_path) = &field.nickel_path {
                    if let Some(value) =
                        Self::extract_value_by_path(&Value::Object(obj.clone()), nickel_path)
                    {
                        defaults.insert(field.name.clone(), value);
                    }
                }
            }
        }

        if verbose && !defaults.is_empty() {
            eprintln!(
                "[roundtrip] Extracted {} default values from {} fields with nickel_path",
                defaults.len(),
                fields_with_paths.len()
            );
        }

        Ok(defaults)
    }

    /// Extract a value from nested JSON using a path
    fn extract_value_by_path(json: &Value, path: &[String]) -> Option<Value> {
        let mut current = json;

        for key in path {
            match current {
                Value::Object(map) => {
                    current = map.get(key)?;
                }
                _ => return None,
            }
        }

        Some(current.clone())
    }
}

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

    #[test]
    fn test_roundtrip_config_creation() {
        let config = RoundtripConfig::new(
            PathBuf::from("input.ncl"),
            PathBuf::from("form.toml"),
            PathBuf::from("output.ncl"),
        );

        assert_eq!(config.input_ncl, PathBuf::from("input.ncl"));
        assert_eq!(config.form_path, PathBuf::from("form.toml"));
        assert_eq!(config.output_ncl, PathBuf::from("output.ncl"));
        assert!(config.validate);
    }

    #[test]
    fn test_extract_value_by_path() {
        use serde_json::json;

        let json = json!({
            "ci": {
                "project": {
                    "name": "test-project"
                }
            }
        });

        let path = vec!["ci".to_string(), "project".to_string(), "name".to_string()];
        let result = RoundtripConfig::extract_value_by_path(&json, &path);

        assert!(result.is_some());
        assert_eq!(result.unwrap(), json!("test-project"));
    }

    #[test]
    fn test_extract_value_by_path_missing() {
        use serde_json::json;

        let json = json!({
            "ci": {
                "project": {}
            }
        });

        let path = vec!["ci".to_string(), "project".to_string(), "name".to_string()];
        let result = RoundtripConfig::extract_value_by_path(&json, &path);

        assert!(result.is_none());
    }
}
