use crate::agent::Agent;
use crate::agent::boilerplate::BoilerPlate;
use crate::config::{Config, check_env_vars};
use crate::create_minimal_blank_agent;
use crate::crypt::KeyManager;
use crate::dns::bootstrap as dns_bootstrap;
use crate::error::JacsError;
use crate::storage::MultiStorage;
use crate::storage::jenv::set_env_var;
use rpassword::read_password;
use serde_json::{Value, json};
use std::env;
use std::io;
use std::path::Path;

use crate::simple::{AgentInfo, CreateAgentParams, SimpleAgent};

const CLI_PASSWORD_FILE_ENV: &str = "JACS_PASSWORD_FILE";

/// Programmatic agent creation for non-interactive use.
///
/// Accepts pre-built `CreateAgentParams` and delegates to `SimpleAgent::create_with_params()`.
/// Use this when integrating CLI commands with the programmatic API.
pub fn handle_agent_create_programmatic(params: CreateAgentParams) -> Result<AgentInfo, JacsError> {
    let (_agent, info) = SimpleAgent::create_with_params(params)?;
    Ok(info)
}

fn request_string(message: &str, default: &str) -> String {
    let mut input = String::new();
    println!("{}: (default: {})", message, default);

    match io::stdin().read_line(&mut input) {
        Ok(_) => {
            let trimmed = input.trim();
            if trimmed.is_empty() {
                default.to_string() // Return default if no input
            } else {
                trimmed.to_string() // Return trimmed input if there's any
            }
        }
        Err(_) => default.to_string(), // Return default on error
    }
}

fn resolve_cli_password_for_config_create() -> Result<Option<String>, JacsError> {
    let env_password = match env::var("JACS_PRIVATE_KEY_PASSWORD") {
        Ok(value) => {
            if value.trim().is_empty() {
                return Err(
                    "JACS_PRIVATE_KEY_PASSWORD is set but empty. Provide a non-empty password."
                        .into(),
                );
            }
            Some(value)
        }
        Err(std::env::VarError::NotPresent) => None,
        Err(std::env::VarError::NotUnicode(_)) => {
            return Err("JACS_PRIVATE_KEY_PASSWORD contains non-UTF-8 data.".into());
        }
    };

    let password_file = match env::var(CLI_PASSWORD_FILE_ENV) {
        Ok(value) => {
            if value.trim().is_empty() {
                return Err(format!(
                    "{} is set but empty. Provide a non-empty file path.",
                    CLI_PASSWORD_FILE_ENV
                )
                .into());
            }
            Some(value)
        }
        Err(std::env::VarError::NotPresent) => None,
        Err(std::env::VarError::NotUnicode(_)) => {
            return Err(format!("{} contains non-UTF-8 data.", CLI_PASSWORD_FILE_ENV).into());
        }
    };

    // Env var takes priority. If both are set, warn but use env var.
    if let Some(env_password) = env_password {
        if password_file.is_some() {
            eprintln!(
                "Warning: both JACS_PRIVATE_KEY_PASSWORD and {} are set. \
                 Using JACS_PRIVATE_KEY_PASSWORD (highest priority).",
                CLI_PASSWORD_FILE_ENV
            );
        }
        println!("Using password from JACS_PRIVATE_KEY_PASSWORD environment variable.");
        return Ok(Some(env_password));
    }

    if let Some(path) = password_file {
        let password_path = Path::new(path.trim());
        let raw = std::fs::read_to_string(password_path).map_err(|e| {
            format!(
                "Failed to read {} at '{}': {}",
                CLI_PASSWORD_FILE_ENV,
                password_path.display(),
                e
            )
        })?;
        // Preserve intentional leading/trailing spaces in passphrases; strip only line endings.
        let password = raw.trim_end_matches(|c| c == '\n' || c == '\r').to_string();
        if password.is_empty() {
            return Err(format!(
                "{} at '{}' is empty.",
                CLI_PASSWORD_FILE_ENV,
                password_path.display()
            )
            .into());
        }

        println!(
            "Using password from {} ('{}').",
            CLI_PASSWORD_FILE_ENV,
            password_path.display()
        );
        return Ok(Some(password));
    }

    // Note: OS keychain lookup requires an agent_id and is handled at agent
    // creation time, not during config creation.

    Ok(None)
}

// Function to handle the 'config create' logic
pub fn handle_config_create() -> Result<(), JacsError> {
    println!("Welcome to the JACS Config Generator!");
    let storage: MultiStorage = MultiStorage::default_new().expect("Failed to initialize storage");

    println!("Enter the path to the agent file if it already exists (leave empty to skip):");
    let mut agent_filename = String::new();
    io::stdin().read_line(&mut agent_filename).unwrap();
    agent_filename = agent_filename.trim().to_string();

    let jacs_agent_id_and_version = if !agent_filename.is_empty() {
        // Use storage to check and read the agent file
        match storage.file_exists(&agent_filename, None) {
            Ok(true) => match storage.get_file(&agent_filename, None) {
                Ok(agent_content_bytes) => match String::from_utf8(agent_content_bytes) {
                    Ok(agent_content) => match serde_json::from_str::<Value>(&agent_content) {
                        Ok(agent_json) => {
                            let jacs_id = agent_json["jacsId"].as_str().unwrap_or("");
                            let jacs_version = agent_json["jacsVersion"].as_str().unwrap_or("");
                            format!("{}:{}", jacs_id, jacs_version)
                        }
                        Err(e) => {
                            println!("Error parsing agent JSON from {}: {}", agent_filename, e);
                            String::new()
                        }
                    },
                    Err(e) => {
                        println!(
                            "Error converting agent file content to UTF-8 {}: {}",
                            agent_filename, e
                        );
                        String::new()
                    }
                },
                Err(e) => {
                    println!("Failed to read agent file {}: {}", agent_filename, e);
                    String::new()
                }
            },
            Ok(false) => {
                println!(
                    "Agent file {} not found in storage. Skipping...",
                    agent_filename
                );
                String::new()
            }
            Err(e) => {
                println!(
                    "Error checking existence of agent file {}: {}",
                    agent_filename, e
                );
                String::new()
            }
        }
    } else {
        String::new()
    };

    // --- Check if config file already exists ---
    let config_path = "jacs.config.json";
    if std::fs::symlink_metadata(config_path).is_ok() {
        return Err(format!(
            "Configuration file '{}' already exists. Please remove or rename it if you want to create a new one.",
            config_path
        )
        .into());
    }
    // --- End check ---

    let jacs_agent_private_key_filename =
        request_string("Enter the private key filename:", "jacs.private.pem.enc");
    let jacs_agent_public_key_filename =
        request_string("Enter the public key filename:", "jacs.public.pem");
    let jacs_agent_key_algorithm = request_string(
        "Enter the agent key algorithm (pq2025 or ring-Ed25519)",
        "pq2025",
    );
    let jacs_default_storage = request_string("Enter the default storage (fs, aws, hai)", "fs");

    // Check for password in environment/bootstrap source first
    let jacs_private_key_password = match resolve_cli_password_for_config_create()? {
        Some(password) => password,
        None => {
            // No non-interactive source set; prompt user interactively.
            println!(
                "\nNo password source configured. Set exactly one non-interactive source:\n  \
                 export JACS_PRIVATE_KEY_PASSWORD='your-strong-password'\n  \
                 export {}=/path/to/password\n  \
                 # or: export JACS_PRIVATE_KEY_PASSWORD=\"$(cat /path/to/password)\"\n",
                CLI_PASSWORD_FILE_ENV
            );
            println!("{}", crate::crypt::aes_encrypt::password_requirements());
            loop {
                println!("Please enter a password (used to encrypt private key):");
                let password = match read_password() {
                    Ok(pass) => pass,
                    Err(e) => {
                        eprintln!("Error reading password: {}. Please try again.", e);
                        continue;
                    }
                };

                if password.is_empty() {
                    eprintln!("Password cannot be empty. Please try again.");
                    continue;
                }

                println!("Please confirm the password:");
                let password_confirm = match read_password() {
                    Ok(pass) => pass,
                    Err(e) => {
                        eprintln!(
                            "Error reading confirmation password: {}. Please start over.",
                            e
                        );
                        continue; // Ask again from the beginning
                    }
                };

                if password == password_confirm {
                    break password; // Passwords match and are not empty, exit loop
                } else {
                    eprintln!("Passwords do not match. Please try again.");
                    // Loop continues
                }
            }
        }
    };

    // Note: keychain storage is deferred to agent creation, which has
    // the agent_id needed for per-agent keychain entries.

    let jacs_use_security = request_string("Use experimental security features", "false");
    let jacs_data_directory = request_string("Directory for data storage", "./jacs");
    let jacs_key_directory = request_string("Directory for keys", "./jacs_keys");
    let jacs_agent_domain = request_string(
        "Agent domain for DNSSEC fingerprint (optional, e.g., example.com)",
        "",
    );

    let mut config = Config::new(
        Some(jacs_use_security),
        Some(jacs_data_directory),
        Some(jacs_key_directory),
        Some(jacs_agent_private_key_filename),
        Some(jacs_agent_public_key_filename),
        Some(jacs_agent_key_algorithm),
        Some(jacs_private_key_password),
        Some(jacs_agent_id_and_version),
        Some(jacs_default_storage),
    );

    // insert optional domain if provided
    if !jacs_agent_domain.trim().is_empty() {
        // Serialize to Value, add field, then write
        let mut v = serde_json::to_value(&config).unwrap_or(serde_json::json!({}));
        if let Some(obj) = v.as_object_mut() {
            obj.insert(
                "jacs_agent_domain".to_string(),
                serde_json::Value::String(jacs_agent_domain.trim().to_string()),
            );
        }
        config = serde_json::from_value(v).unwrap_or(config);
    }

    // Serialize, but ensure we omit any null fields that may have slipped through
    let mut value = serde_json::to_value(&config).unwrap_or(serde_json::json!({}));
    if let Some(obj) = value.as_object_mut() {
        // Remove optional domain if it ended up as null
        if obj.get("jacs_agent_domain").is_some_and(|v| v.is_null()) {
            obj.remove("jacs_agent_domain");
        }
    }
    let serialized = serde_json::to_string_pretty(&value).unwrap();

    // Keep using std::fs for config file backup and writing
    // The check and backup logic below is no longer needed as we exit earlier if the file exists.
    /*
    let config_path = "jacs.config.json"; // This line is already defined above
    if metadata(config_path).is_ok() {
        // Keep std::fs::metadata
        let now: DateTime<Local> = Local::now();
        let backup_path = format!("{}-backup-jacs.config.json", now.format("%Y%m%d%H%M%S"));
        rename(config_path, backup_path.clone()).unwrap(); // Keep std::fs::rename
        println!("Backed up existing jacs.config.json to {}", backup_path);
    }
    */

    crate::secure_io::write_new_file(config_path, serialized.as_bytes(), 0o644)
        .map_err(|e| format!("Failed to create config file '{}': {}", config_path, e))?;

    println!("jacs.config.json file generated successfully!");
    Ok(())
}

// Function to handle the 'agent create' logic
pub fn handle_agent_create(filename: Option<&String>, create_keys: bool) -> Result<(), JacsError> {
    handle_agent_create_inner(filename, create_keys, false)
}

/// Like `handle_agent_create` but when `auto_update_config` is true, automatically
/// sets the new agent ID in `jacs.config.json` without prompting.
pub fn handle_agent_create_auto(
    filename: Option<&String>,
    create_keys: bool,
    auto_update_config: bool,
) -> Result<(), JacsError> {
    handle_agent_create_inner(filename, create_keys, auto_update_config)
}

fn handle_agent_create_inner(
    filename: Option<&String>,
    create_keys: bool,
    auto_update_config: bool,
) -> Result<(), JacsError> {
    let storage: MultiStorage = MultiStorage::default_new().expect("Failed to initialize storage");
    let config_path_str = "jacs.config.json";
    let mut agent = if Path::new(config_path_str).exists() {
        println!("Loading configuration from {}...", config_path_str);
        Agent::builder()
            .config_path(config_path_str)
            .build()
            .map_err(|e| {
                format!(
                    "Failed to initialize agent from config '{}': {}",
                    config_path_str, e
                )
            })?
    } else {
        println!(
            "{} not found, proceeding with defaults or environment variables.",
            config_path_str
        );
        Agent::builder()
            .build()
            .map_err(|e| format!("Failed to initialize agent with defaults: {}", e))?
    };

    // publish_to_env() removed: Agent now carries key_paths from config,
    // so FsEncryptedStore uses Agent.key_paths() instead of env reads.

    // -- Get user input for agent type and SERVICE descriptions --
    let agent_type = request_string("Agent Type (e.g., ai, person, service, device)", "ai"); // Default to ai
    if agent_type.is_empty() {
        return Err("Agent type cannot be empty.".into());
    }
    // TODO: Validate agent_type against schema enum: ["human", "human-org", "hybrid", "ai"]

    let service_description = request_string(
        "Service Description",
        "Describe a service the agent provides",
    );
    let success_description = request_string(
        "Service Success Description",
        "Describe a success of the service",
    );
    let failure_description = request_string(
        "Service Failure Description",
        "Describe what failure is of the service",
    );

    // Variables for service descriptions when creating minimal agent
    let (minimal_service_desc, minimal_success_desc, minimal_failure_desc) = if filename.is_none() {
        // Use descriptions collected from user only if creating minimal agent
        (
            Some(service_description),
            Some(success_description),
            Some(failure_description),
        )
    } else {
        // If loading from file, pass None (template should contain service info)
        (None, None, None)
    };

    // TODO output instructions for updating agent definition

    // Load or create base agent string
    let agent_template_string = match filename {
        Some(fname) => {
            let content_bytes = storage
                .get_file(fname, None)
                .map_err(|e| format!("Failed to load agent template file '{}': {}", fname, e))?;
            String::from_utf8(content_bytes)
                .map_err(|e| format!("Agent template file {} is not valid UTF-8: {}", fname, e))?
        }
        _ => create_minimal_blank_agent(
            agent_type.clone(),   // Pass the collected agent_type
            minimal_service_desc, // Pass collected service description
            minimal_success_desc, // Pass collected success description
            minimal_failure_desc, // Pass collected failure description
        )
        .map_err(|e| format!("Failed to create minimal agent template: {}", e))?,
    };

    // -- Modify the agent template with remaining user input (agent_type) --
    let mut agent_json: Value = serde_json::from_str(&agent_template_string).map_err(|e| {
        format!(
            "Failed to parse agent template JSON: {}\nTemplate content:\n{}",
            e, agent_template_string
        )
    })?;

    // Add or update fields - ONLY agent_type remains needed here as name/desc removed
    if let Some(obj) = agent_json.as_object_mut() {
        // obj.insert("jacsName".to_string(), json!(agent_name)); // Removed
        // obj.insert("jacsDescription".to_string(), json!(agent_description)); // Removed
        obj.insert("jacsAgentType".to_string(), json!(agent_type)); // Use jacsAgentType based on schema
    } else {
        return Err("Agent template is not a valid JSON object.".into());
    }

    let modified_agent_string = serde_json::to_string(&agent_json)?;

    // Proceed with agent creation using the already-initialized config-backed agent.
    println!("Proceeding with agent creation using loaded configuration/environment variables.");

    if create_keys {
        println!("Creating keys...");
        agent.generate_keys()?;
        println!(
            "Keys created in {}. Don't loose them! Keep them in a safe place. ",
            agent
                .config
                .as_ref()
                .unwrap()
                .jacs_key_directory()
                .as_deref()
                .unwrap_or_default()
        );
        // If a domain is configured, emit DNS fingerprint instructions (non-strict at creation time)
        agent.set_dns_strict(false);
        if let Some(domain) = agent
            .config
            .as_ref()
            .and_then(|c| c.jacs_agent_domain().clone())
            .filter(|s| !s.is_empty())
            && let Ok(pk) = agent.get_public_key()
        {
            let agent_id = agent.get_id().unwrap_or_else(|_| "".to_string());
            let digest = dns_bootstrap::pubkey_digest_b64(&pk);
            let rr = dns_bootstrap::build_dns_record(
                &domain,
                3600,
                &agent_id,
                &digest,
                dns_bootstrap::DigestEncoding::Base64,
            );
            println!("\nDNS (BIND):\n{}\n", dns_bootstrap::emit_plain_bind(&rr));
            println!(
                "Use 'jacs agent dns --domain {} --provider <plain|aws|azure|cloudflare>' for provider-specific commands.",
                domain
            );
            println!("Reminder: enable DNSSEC for the zone and publish DS at the registrar.");
        }
    }

    // Use the modified agent string here
    agent.create_agent_and_load(&modified_agent_string, false, None)?;

    let agent_id_version = agent.get_lookup_id()?;
    println!("Agent {} created successfully!", agent_id_version);

    agent.save()?;

    // -- Determine whether to update the config --
    let should_update = if auto_update_config {
        true
    } else {
        let prompt_message = format!(
            "Do you want to set {} as the default agent in jacs.config.json and environment variable? (yes/no)",
            agent_id_version
        );
        let update_confirmation = request_string(&prompt_message, "no");
        update_confirmation.trim().to_lowercase() == "yes"
            || update_confirmation.trim().to_lowercase() == "y"
    };

    if should_update {
        println!("Updating configuration...");
        let config_path_str = "jacs.config.json";
        let config_path = Path::new(config_path_str);

        // Use std::fs for reading
        let mut current_config: Value = match std::fs::read_to_string(config_path) {
            Ok(content) => serde_json::from_str(&content).unwrap_or_else(|e| {
                println!(
                    "Warning: Could not parse {}, creating default. Error: {}",
                    config_path_str, e
                );
                json!({}) // Start with empty object if parse fails or file empty
            }),
            Err(ref e) if e.kind() == io::ErrorKind::NotFound => {
                println!("Warning: {} not found, creating default.", config_path_str);
                json!({}) // Start with empty object if file doesn't exist
            }
            Err(e) => {
                eprintln!("Error reading {}: {}. Cannot update.", config_path_str, e);
                return Ok(()); // Exit this block gracefully if read fails for other reasons
            }
        };

        if !current_config.is_object() {
            println!(
                "Warning: {} content is not a JSON object. Overwriting with default structure.",
                config_path_str
            );
            current_config = json!({});
        }

        if let Some(obj) = current_config.as_object_mut() {
            obj.insert(
                "jacs_agent_id_and_version".to_string(),
                json!(agent_id_version),
            );
            if !obj.contains_key("$schema") {
                obj.insert(
                    "$schema".to_string(),
                    json!("https://hai.ai/schemas/jacs.config.schema.json"),
                );
            }
        }

        // Sign the config with the agent's key (two-pass pattern: write fields,
        // then sign). If the config already has a jacsSignature, update_config
        // bumps the version; otherwise sign_config creates the initial signature.
        let signed_config = if current_config.get("jacsSignature").is_some() {
            agent.update_config(&current_config)
        } else {
            agent.sign_config(&current_config)
        };
        match signed_config {
            Ok(signed) => {
                current_config = signed;
            }
            Err(e) => {
                eprintln!("Warning: failed to sign config: {}. Writing unsigned.", e);
            }
        }

        // Use std::fs for writing
        match std::fs::write(
            config_path,
            serde_json::to_string_pretty(&current_config).unwrap(),
        ) {
            Ok(_) => println!("Successfully updated {}.", config_path_str),
            Err(e) => eprintln!("Error writing {}: {}", config_path_str, e),
        }

        // Update environment variables for the current session
        match set_env_var("JACS_AGENT_ID_AND_VERSION", &agent_id_version) {
            Ok(_) => {
                println!("Updated JACS_AGENT_ID_AND_VERSION environment variable for this session.")
            }
            Err(e) => eprintln!(
                "Failed to update JACS_AGENT_ID_AND_VERSION environment variable: {}",
                e
            ),
        }
        match check_env_vars(false) {
            Ok(report) => println!("Environment Variable Check:\n{}", report),
            Err(e) => {
                eprintln!("Error checking environment variables after update: {}", e)
            }
        }
    } else {
        println!("Skipping configuration update.");
    }
    Ok(())
}
