//! Generate command: orchestrates the provisioning generation pipeline.

use crate::config::Config;
use crate::error::Result;
use crate::input::{CargoIntrospector, ConfigLoader, NickelSchemaLoader};
use crate::models::ProjectSpec;
use crate::template::TemplateLoader;
use std::path::PathBuf;
use tracing::{debug, info};

/// The Generate command orchestrates the entire provisioning generation pipeline.
pub struct GenerateCommand;

impl GenerateCommand {
    /// Execute the generate command with specified parameters.
    pub async fn execute(
        mode: &str,
        input: Option<PathBuf>,
        output: PathBuf,
        project: Option<String>,
        dry_run: bool,
    ) -> Result<()> {
        info!("Starting provisioning generation (mode: {})", mode);

        // Step 1: Load or infer ProjectSpec based on input mode
        let spec = Self::load_spec(mode, input, project).await?;

        debug!(
            "Loaded ProjectSpec: {} ({:?})",
            spec.name, spec.project_type
        );

        // Step 2: Validate the spec
        spec.validate().map_err(|errors| {
            crate::error::ProvisioningGenError::Other(format!(
                "Invalid specification: {}",
                errors.join(", ")
            ))
        })?;

        info!("ProjectSpec validated successfully");

        // Step 3: Generate provisioning structure
        if dry_run {
            info!("DRY RUN: Would generate to {}", output.display());
            info!("Project: {}", spec.name);
            info!("Type: {:?}", spec.project_type);
            info!(
                "Features: {:?}",
                spec.domain_features
                    .iter()
                    .map(|f| &f.name)
                    .collect::<Vec<_>>()
            );
            return Ok(());
        }

        // Ensure output directory exists
        std::fs::create_dir_all(&output).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create output directory: {}",
                e
            ))
        })?;

        info!("Generating provisioning structure to {}", output.display());

        // Initialize template loader for generators that need it
        let config = Config::default();
        let template_loader = TemplateLoader::new(&config)?;

        // Execute the complete generation pipeline in dependency order
        use crate::generator::{
            ConstraintGenerator, DefaultsGenerator, DocumentationGenerator, FormComposerGenerator,
            FragmentGenerator, IacTemplateGenerator, LocalesGenerator, SchemaGenerator,
            ScriptGenerator, ValidatorGenerator, ValuesGenerator,
        };

        // Layer 1: Constraints (foundation - needed by validators and fragments)
        ConstraintGenerator::generate(&spec, &output)?;
        debug!("✓ Constraints");

        // Layer 2: Schemas (domain types - needed by defaults and validators)
        SchemaGenerator::generate(&spec, &output)?;
        debug!("✓ Schemas");

        // Layer 3: Validators (validation logic)
        ValidatorGenerator::generate(&spec, &output)?;
        debug!("✓ Validators");

        // Layer 4: Defaults (sensible defaults - needed by values)
        DefaultsGenerator::generate(&spec, &output)?;
        debug!("✓ Defaults");

        // Layer 5: Fragments (form UI - needed by form composer)
        FragmentGenerator::generate(&spec, &output)?;
        debug!("✓ Fragments");

        // Layer 6: Scripts (orchestration)
        ScriptGenerator::generate(&spec, &output, &template_loader)?;
        debug!("✓ Scripts");

        // Layer 7: Values (runtime separation)
        ValuesGenerator::generate(&spec, &output)?;
        debug!("✓ Values");

        // Layer 8: Locales (i18n support)
        LocalesGenerator::generate(&spec, &output)?;
        debug!("✓ Locales");

        // Layer 9: Form Composer (master form configuration)
        FormComposerGenerator::generate(&spec, &output)?;
        debug!("✓ Form Composer");

        // Layer 10: IaC Templates (infrastructure)
        IacTemplateGenerator::generate(&spec, &output, &template_loader)?;
        debug!("✓ IaC Templates");

        // Layer 11: Documentation (comprehensive docs)
        DocumentationGenerator::generate(&spec, &output, &template_loader)?;
        debug!("✓ Documentation");

        info!("Provisioning generation completed successfully!");
        info!("Generated {} generators across 11 layers", 11);
        info!("Generated structure at: {}", output.display());

        Ok(())
    }

    /// Load ProjectSpec from the specified input mode.
    async fn load_spec(
        mode: &str,
        input: Option<PathBuf>,
        project_override: Option<String>,
    ) -> Result<ProjectSpec> {
        match mode.to_lowercase().as_str() {
            "cargo" => {
                let cargo_path = input.unwrap_or_else(|| PathBuf::from("Cargo.toml"));
                debug!("Loading from Cargo.toml: {}", cargo_path.display());
                CargoIntrospector::analyze(&cargo_path)
            }

            "config" => {
                let config_path = input.ok_or_else(|| {
                    crate::error::ProvisioningGenError::Other(
                        "Config mode requires --input parameter".to_string(),
                    )
                })?;
                debug!("Loading from config file: {}", config_path.display());
                ConfigLoader::load(&config_path)
            }

            "nickel" => {
                let schema_path = input.ok_or_else(|| {
                    crate::error::ProvisioningGenError::Other(
                        "Nickel mode requires --input parameter".to_string(),
                    )
                })?;
                debug!("Loading from Nickel schema: {}", schema_path.display());
                NickelSchemaLoader::load(&schema_path)
            }

            "wizard" => {
                use crate::ai::InteractiveWizard;
                debug!("Starting interactive wizard");
                InteractiveWizard::run(project_override.clone()).await
            }

            other => Err(crate::error::ProvisioningGenError::Other(format!(
                "Unknown mode: {}. Use: cargo, config, nickel, or wizard",
                other
            )))?,
        }
        .map(|mut spec| {
            // Apply project name override if provided
            if let Some(name) = project_override {
                spec.name = name;
            }
            spec
        })
    }
}
