//! Rhai wrappers for Buildah module functions
//!
//! This module provides Rhai wrappers for the functions in the Buildah module.
//!
//! ## Fluent Builder API
//!
//! The `Bah` type provides a clean, chainable API for building container images:
//!
//! ```rhai
//! // Create and configure a container with method chaining
//! let image = bah_new("mycontainer", "ubuntu:22.04")
//!     .run("apt update")
//!     .run("apt install -y nginx")
//!     .copy("./config", "/etc/nginx/")
//!     .write_content("Hello World", "/var/www/html/index.html")
//!     .set_entrypoint("/usr/sbin/nginx")
//!     .set_cmd("-g 'daemon off;'")
//!     .commit("myimage:latest");
//!
//! // Access the last command result
//! print(image.stdout);
//! ```

use crate::buildah::{BuildahError, Builder, ContentOperations, Image, RemoteExecutor};
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};
use crate::process::CommandResult;
use std::collections::HashMap;

// ============================================================================
// Fluent Builder API - Bah
// ============================================================================

/// Fluent wrapper for Builder that enables method chaining in Rhai.
///
/// All methods return `Self` to enable chaining. Errors are thrown immediately.
/// The last `CommandResult` is stored and accessible via the `result` property.
#[derive(Clone)]
pub struct Bah {
    builder: Builder,
    last_result: Option<CommandResult>,
}

impl Bah {
    /// Create a new Bah instance from a Builder
    pub fn new(builder: Builder) -> Self {
        Self {
            builder,
            last_result: None,
        }
    }

    /// Store the result and return self for chaining
    fn with_result(mut self, result: CommandResult) -> Self {
        self.last_result = Some(result);
        self
    }
}

// ============================================================================
// Fluent API Functions for Rhai
// ============================================================================

/// Create a new Bah builder (fluent API entry point)
///
/// # Example
/// ```rhai
/// let b = bah("mycontainer", "ubuntu:22.04");
/// ```
pub fn bah_fluent_new(name: &str, image: &str) -> Result<Bah, Box<EvalAltResult>> {
    let builder = bah_error_to_rhai_error(Builder::new(name, image))?;
    Ok(Bah::new(builder))
}

/// Create a new Bah builder with a remote executor (fluent API entry point for remote execution)
///
/// This allows running buildah commands on remote systems via SSH, kubectl exec, etc.
///
/// # Example
/// ```rhai
/// let executor = kubectl_executor("namespace", "pod");
/// let b = bah_with_executor("mycontainer", "ubuntu:22.04", executor);
/// ```
pub fn bah_fluent_new_with_executor<E: RemoteExecutor + 'static>(
    name: &str,
    image: &str,
    executor: E,
) -> Result<Bah, Box<EvalAltResult>> {
    let builder = bah_error_to_rhai_error(Builder::with_executor(name, image, executor))?;
    Ok(Bah::new(builder))
}

/// Run a command in the container (chainable)
pub fn bah_run(bah: Bah, command: &str) -> Result<Bah, Box<EvalAltResult>> {
    let result = bah_error_to_rhai_error(bah.builder.run(command))?;
    Ok(bah.with_result(result))
}

/// Run a command with isolation (chainable)
pub fn bah_run_with_isolation(
    bah: Bah,
    command: &str,
    isolation: &str,
) -> Result<Bah, Box<EvalAltResult>> {
    let result = bah_error_to_rhai_error(bah.builder.run_with_isolation(command, isolation))?;
    Ok(bah.with_result(result))
}

/// Copy files into the container (chainable)
pub fn bah_copy(bah: Bah, source: &str, dest: &str) -> Result<Bah, Box<EvalAltResult>> {
    let result = bah_error_to_rhai_error(bah.builder.copy(source, dest))?;
    Ok(bah.with_result(result))
}

/// Add files into the container (chainable)
pub fn bah_add(bah: Bah, source: &str, dest: &str) -> Result<Bah, Box<EvalAltResult>> {
    let result = bah_error_to_rhai_error(bah.builder.add(source, dest))?;
    Ok(bah.with_result(result))
}

/// Configure container metadata (chainable)
pub fn bah_config(bah: Bah, options: Map) -> Result<Bah, Box<EvalAltResult>> {
    let config_options = convert_map_to_hashmap(options)?;
    let result = bah_error_to_rhai_error(bah.builder.config(config_options))?;
    Ok(bah.with_result(result))
}

/// Set the entrypoint (chainable)
pub fn bah_set_entrypoint(bah: Bah, entrypoint: &str) -> Result<Bah, Box<EvalAltResult>> {
    let result = bah_error_to_rhai_error(bah.builder.set_entrypoint(entrypoint))?;
    Ok(bah.with_result(result))
}

/// Set the default command (chainable)
pub fn bah_set_cmd(bah: Bah, cmd: &str) -> Result<Bah, Box<EvalAltResult>> {
    let result = bah_error_to_rhai_error(bah.builder.set_cmd(cmd))?;
    Ok(bah.with_result(result))
}

/// Write content to a file in the container (chainable)
pub fn bah_write_content(bah: Bah, content: &str, dest_path: &str) -> Result<Bah, Box<EvalAltResult>> {
    if let Some(container_id) = bah.builder.container_id() {
        let result = bah_error_to_rhai_error(ContentOperations::write_content(
            container_id,
            content,
            dest_path,
        ))?;
        Ok(bah.with_result(result))
    } else {
        Err(Box::new(EvalAltResult::ErrorRuntime(
            "No container ID available".into(),
            rhai::Position::NONE,
        )))
    }
}

/// Commit the container to an image (chainable)
pub fn bah_commit(bah: Bah, image_name: &str) -> Result<Bah, Box<EvalAltResult>> {
    let result = bah_error_to_rhai_error(bah.builder.commit(image_name))?;
    Ok(bah.with_result(result))
}

/// Remove the container (terminal)
pub fn bah_remove(bah: &mut Bah) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(bah.builder.remove())
}

/// Reset the builder (terminal)
pub fn bah_reset(bah: &mut Bah) -> Result<(), Box<EvalAltResult>> {
    bah_error_to_rhai_error(bah.builder.reset())
}

/// Read content from a file in the container
pub fn bah_read_content(bah: &mut Bah, source_path: &str) -> Result<String, Box<EvalAltResult>> {
    if let Some(container_id) = bah.builder.container_id() {
        bah_error_to_rhai_error(ContentOperations::read_content(container_id, source_path))
    } else {
        Err(Box::new(EvalAltResult::ErrorRuntime(
            "No container ID available".into(),
            rhai::Position::NONE,
        )))
    }
}

// Property getters for Bah

/// Get the container ID
pub fn bah_get_container_id(bah: &mut Bah) -> String {
    bah.builder.container_id().cloned().unwrap_or_default()
}

/// Get the container name
pub fn bah_get_name(bah: &mut Bah) -> String {
    bah.builder.name().to_string()
}

/// Get the base image
pub fn bah_get_image(bah: &mut Bah) -> String {
    bah.builder.image().to_string()
}

/// Get the last command result
pub fn bah_get_result(bah: &mut Bah) -> Dynamic {
    match &bah.last_result {
        Some(result) => Dynamic::from(result.clone()),
        None => Dynamic::UNIT,
    }
}

/// Get stdout from the last command
pub fn bah_get_stdout(bah: &mut Bah) -> String {
    bah.last_result
        .as_ref()
        .map(|r| r.stdout.clone())
        .unwrap_or_default()
}

/// Get stderr from the last command
pub fn bah_get_stderr(bah: &mut Bah) -> String {
    bah.last_result
        .as_ref()
        .map(|r| r.stderr.clone())
        .unwrap_or_default()
}

/// Get success status from the last command
pub fn bah_get_success(bah: &mut Bah) -> bool {
    bah.last_result.as_ref().map(|r| r.success).unwrap_or(true)
}

/// Get exit code from the last command
pub fn bah_get_code(bah: &mut Bah) -> i32 {
    bah.last_result.as_ref().map(|r| r.code).unwrap_or(0)
}

/// Get debug mode
pub fn bah_get_debug(bah: &mut Bah) -> bool {
    bah.builder.debug()
}

/// Set debug mode
pub fn bah_set_debug(bah: &mut Bah, debug: bool) {
    bah.builder.set_debug(debug);
}

// ============================================================================
// Legacy API Functions (for backward compatibility)
// ============================================================================

/// Register Buildah module functions with the Rhai engine
///
/// # Arguments
///
/// * `engine` - The Rhai engine to register the functions with
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
pub fn register_bah_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    // Register types
    register_bah_types(engine)?;

    // Register the new fluent Bah API
    register_bah_fluent_api(engine)?;

    // Register Builder constructor (legacy, returns Builder)
    engine.register_fn("bah_new", bah_new);

    // Register Builder instance methods (legacy)
    engine.register_fn("run", builder_run);
    engine.register_fn("run_with_isolation", builder_run_with_isolation);
    engine.register_fn("copy", builder_copy);
    engine.register_fn("add", builder_add);
    engine.register_fn("commit", builder_commit);
    engine.register_fn("remove", builder_remove);
    engine.register_fn("reset", builder_reset);
    engine.register_fn("config", builder_config);
    // Register Builder instance methods for entrypoint, cmd, and content operations
    engine.register_fn("set_entrypoint", builder_set_entrypoint);
    engine.register_fn("set_cmd", builder_set_cmd);
    engine.register_fn("write_content", builder_write_content);
    engine.register_fn("read_content", builder_read_content);

    // Register Builder static methods
    engine.register_fn("images", builder_images);
    engine.register_fn("image_remove", builder_image_remove);
    engine.register_fn("image_pull", builder_image_pull);
    engine.register_fn("image_push", builder_image_push);
    engine.register_fn("image_tag", builder_image_tag);
    engine.register_fn("build", builder_build);
    engine.register_fn("read_content", builder_read_content);

    Ok(())
}

/// Register the fluent Bah API for method chaining
fn register_bah_fluent_api(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    // Register Bah type
    engine.register_type_with_name::<Bah>("Bah");

    // Constructor - returns Bah for chaining
    engine.register_fn("bah", bah_fluent_new);

    // Chainable methods - all return Bah
    engine.register_fn("run", bah_run);
    engine.register_fn("sh", bah_run); // Alias for run
    engine.register_fn("run_with_isolation", bah_run_with_isolation);
    engine.register_fn("copy", bah_copy);
    engine.register_fn("add", bah_add);
    engine.register_fn("config", bah_config);
    engine.register_fn("set_entrypoint", bah_set_entrypoint);
    engine.register_fn("entrypoint", bah_set_entrypoint); // Alias
    engine.register_fn("set_cmd", bah_set_cmd);
    engine.register_fn("cmd", bah_set_cmd); // Alias
    engine.register_fn("write_content", bah_write_content);
    engine.register_fn("write", bah_write_content); // Alias
    engine.register_fn("commit", bah_commit);

    // Terminal methods
    engine.register_fn("remove", bah_remove);
    engine.register_fn("reset", bah_reset);
    engine.register_fn("read_content", bah_read_content);
    engine.register_fn("read", bah_read_content); // Alias

    // Property getters
    engine.register_get("container_id", bah_get_container_id);
    engine.register_get("name", bah_get_name);
    engine.register_get("image", bah_get_image);
    engine.register_get("result", bah_get_result);
    engine.register_get("stdout", bah_get_stdout);
    engine.register_get("stderr", bah_get_stderr);
    engine.register_get("success", bah_get_success);
    engine.register_get("code", bah_get_code);

    // Debug mode
    engine.register_get("debug", bah_get_debug);
    engine.register_set("debug", bah_set_debug);

    Ok(())
}

/// Register Buildah module types with the Rhai engine
fn register_bah_types(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    // Register Builder type
    engine.register_type_with_name::<Builder>("BuildahBuilder");

    // Register getters for Builder properties
    engine.register_get("container_id", get_builder_container_id);
    engine.register_get("name", get_builder_name);
    engine.register_get("image", get_builder_image);
    engine.register_get("debug_mode", get_builder_debug);
    engine.register_set("debug_mode", set_builder_debug);

    // Register Image type and methods (same as before)
    engine.register_type_with_name::<Image>("BuildahImage");

    // Register getters for Image properties
    engine.register_get("id", |img: &mut Image| img.id.clone());
    engine.register_get("names", |img: &mut Image| {
        let mut array = Array::new();
        for name in &img.names {
            array.push(Dynamic::from(name.clone()));
        }
        array
    });
    // Add a 'name' getter that returns the first name or a default
    engine.register_get("name", |img: &mut Image| {
        if img.names.is_empty() {
            "<none>".to_string()
        } else {
            img.names[0].clone()
        }
    });
    engine.register_get("size", |img: &mut Image| img.size.clone());
    engine.register_get("created", |img: &mut Image| img.created.clone());

    Ok(())
}

// Helper functions for error conversion
fn bah_error_to_rhai_error<T>(result: Result<T, BuildahError>) -> Result<T, Box<EvalAltResult>> {
    result.map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("Buildah error: {}", e).into(),
            rhai::Position::NONE,
        ))
    })
}

// Helper function to convert Rhai Map to Rust HashMap
fn convert_map_to_hashmap(options: Map) -> Result<HashMap<String, String>, Box<EvalAltResult>> {
    let mut config_options = HashMap::<String, String>::new();

    for (key, value) in options.iter() {
        if let Ok(value_str) = value.clone().into_string() {
            // Convert SmartString to String
            config_options.insert(key.to_string(), value_str);
        } else {
            return Err(Box::new(EvalAltResult::ErrorRuntime(
                format!("Option '{}' must be a string", key).into(),
                rhai::Position::NONE,
            )));
        }
    }

    Ok(config_options)
}

/// Create a new Builder
pub fn bah_new(name: &str, image: &str) -> Result<Builder, Box<EvalAltResult>> {
    bah_error_to_rhai_error(Builder::new(name, image))
}

// Builder instance methods
pub fn builder_run(
    builder: &mut Builder,
    command: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.run(command))
}

pub fn builder_run_with_isolation(
    builder: &mut Builder,
    command: &str,
    isolation: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.run_with_isolation(command, isolation))
}

pub fn builder_copy(
    builder: &mut Builder,
    source: &str,
    dest: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.copy(source, dest))
}

pub fn builder_add(
    builder: &mut Builder,
    source: &str,
    dest: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.add(source, dest))
}

pub fn builder_commit(
    builder: &mut Builder,
    image_name: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.commit(image_name))
}

pub fn builder_remove(builder: &mut Builder) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.remove())
}

pub fn builder_config(
    builder: &mut Builder,
    options: Map,
) -> Result<CommandResult, Box<EvalAltResult>> {
    // Convert Rhai Map to Rust HashMap
    let config_options = convert_map_to_hashmap(options)?;
    bah_error_to_rhai_error(builder.config(config_options))
}

/// Set the entrypoint for the container
pub fn builder_set_entrypoint(
    builder: &mut Builder,
    entrypoint: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.set_entrypoint(entrypoint))
}

/// Set the default command for the container
pub fn builder_set_cmd(
    builder: &mut Builder,
    cmd: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.set_cmd(cmd))
}

/// Write content to a file in the container
pub fn builder_write_content(
    builder: &mut Builder,
    content: &str,
    dest_path: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    if let Some(container_id) = builder.container_id() {
        bah_error_to_rhai_error(ContentOperations::write_content(
            container_id,
            content,
            dest_path,
        ))
    } else {
        Err(Box::new(EvalAltResult::ErrorRuntime(
            "No container ID available".into(),
            rhai::Position::NONE,
        )))
    }
}

/// Read content from a file in the container
pub fn builder_read_content(
    builder: &mut Builder,
    source_path: &str,
) -> Result<String, Box<EvalAltResult>> {
    if let Some(container_id) = builder.container_id() {
        bah_error_to_rhai_error(ContentOperations::read_content(container_id, source_path))
    } else {
        Err(Box::new(EvalAltResult::ErrorRuntime(
            "No container ID available".into(),
            rhai::Position::NONE,
        )))
    }
}

// Builder static methods
pub fn builder_images(_builder: &mut Builder) -> Result<Array, Box<EvalAltResult>> {
    let images = bah_error_to_rhai_error(Builder::images())?;

    // Convert Vec<Image> to Rhai Array
    let mut array = Array::new();
    for image in images {
        array.push(Dynamic::from(image));
    }

    Ok(array)
}

pub fn builder_image_remove(
    _builder: &mut Builder,
    image: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(Builder::image_remove(image))
}

pub fn builder_image_pull(
    _builder: &mut Builder,
    image: &str,
    tls_verify: bool,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(Builder::image_pull(image, tls_verify))
}

pub fn builder_image_push(
    _builder: &mut Builder,
    image: &str,
    destination: &str,
    tls_verify: bool,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(Builder::image_push(image, destination, tls_verify))
}

pub fn builder_image_tag(
    _builder: &mut Builder,
    image: &str,
    new_name: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(Builder::image_tag(image, new_name))
}

// Getter functions for Builder properties
pub fn get_builder_container_id(builder: &mut Builder) -> String {
    match builder.container_id() {
        Some(id) => id.clone(),
        None => "".to_string(),
    }
}

pub fn get_builder_name(builder: &mut Builder) -> String {
    builder.name().to_string()
}

pub fn get_builder_image(builder: &mut Builder) -> String {
    builder.image().to_string()
}

/// Get the debug flag from a Builder
pub fn get_builder_debug(builder: &mut Builder) -> bool {
    builder.debug()
}

/// Set the debug flag on a Builder
pub fn set_builder_debug(builder: &mut Builder, debug: bool) {
    builder.set_debug(debug);
}

// Reset function for Builder
pub fn builder_reset(builder: &mut Builder) -> Result<(), Box<EvalAltResult>> {
    bah_error_to_rhai_error(builder.reset())
}

// Build function for Builder
pub fn builder_build(
    _builder: &mut Builder,
    tag: &str,
    context_dir: &str,
    file: &str,
    isolation: &str,
) -> Result<CommandResult, Box<EvalAltResult>> {
    bah_error_to_rhai_error(Builder::build(
        Some(tag),
        context_dir,
        file,
        Some(isolation),
    ))
}
