use crate::cloudhv;
use crate::cloudhv::{VmRecord, VmRuntime, VmSpec};
use rhai::{Array, Dynamic, Engine, EvalAltResult, Map};

// Error adapter
fn hv_to_rhai<T>(r: Result<T, cloudhv::CloudHvError>) -> Result<T, Box<EvalAltResult>> {
    r.map_err(|e| {
        Box::new(EvalAltResult::ErrorRuntime(
            format!("cloudhv error: {}", e).into(),
            rhai::Position::NONE,
        ))
    })
}

// Map conversions

fn map_to_vmspec(spec: Map) -> Result<VmSpec, Box<EvalAltResult>> {
    let id = must_get_string(&spec, "id")?;
    let kernel_path = get_string(&spec, "kernel_path");
    let firmware_path = get_string(&spec, "firmware_path");
    let disk_path = must_get_string(&spec, "disk_path")?;
    let api_socket = get_string(&spec, "api_socket").unwrap_or_else(|| "".to_string());
    let vcpus = get_int(&spec, "vcpus").unwrap_or(1) as u32;
    let memory_mb = get_int(&spec, "memory_mb").unwrap_or(512) as u32;
    let cmdline = get_string(&spec, "cmdline");
    let extra_args = get_string_array(&spec, "extra_args");

    Ok(VmSpec {
        id,
        kernel_path,
        firmware_path,
        disk_path,
        api_socket,
        vcpus,
        memory_mb,
        cmdline,
        extra_args,
    })
}

fn vmspec_to_map(s: &VmSpec) -> Map {
    let mut m = Map::new();
    m.insert("id".into(), s.id.clone().into());
    if let Some(k) = &s.kernel_path {
        m.insert("kernel_path".into(), k.clone().into());
    } else {
        m.insert("kernel_path".into(), Dynamic::UNIT);
    }
    if let Some(fw) = &s.firmware_path {
        m.insert("firmware_path".into(), fw.clone().into());
    } else {
        m.insert("firmware_path".into(), Dynamic::UNIT);
    }
    m.insert("disk_path".into(), s.disk_path.clone().into());
    m.insert("api_socket".into(), s.api_socket.clone().into());
    m.insert("vcpus".into(), (s.vcpus as i64).into());
    m.insert("memory_mb".into(), (s.memory_mb as i64).into());
    if let Some(c) = &s.cmdline {
        m.insert("cmdline".into(), c.clone().into());
    } else {
        m.insert("cmdline".into(), Dynamic::UNIT);
    }
    if let Some(arr) = &s.extra_args {
        let mut a = Array::new();
        for s in arr {
            a.push(s.clone().into());
        }
        m.insert("extra_args".into(), a.into());
    } else {
        m.insert("extra_args".into(), Dynamic::UNIT);
    }
    m
}

fn vmruntime_to_map(r: &VmRuntime) -> Map {
    let mut m = Map::new();
    match r.pid {
        Some(p) => m.insert("pid".into(), (p as i64).into()),
        None => m.insert("pid".into(), Dynamic::UNIT),
    };
    m.insert("status".into(), r.status.clone().into());
    m.insert("log_file".into(), r.log_file.clone().into());
    m
}

fn vmrecord_to_map(rec: &VmRecord) -> Map {
    let mut m = Map::new();
    m.insert("spec".into(), vmspec_to_map(&rec.spec).into());
    m.insert("runtime".into(), vmruntime_to_map(&rec.runtime).into());
    m
}

// Helpers for reading Rhai Map fields

fn must_get_string(m: &Map, k: &str) -> Result<String, Box<EvalAltResult>> {
    match m.get(k) {
        Some(v) if v.is_string() => Ok(v.clone().cast::<String>()),
        _ => Err(Box::new(EvalAltResult::ErrorRuntime(
            format!("missing or non-string field '{}'", k).into(),
            rhai::Position::NONE,
        ))),
    }
}

fn get_string(m: &Map, k: &str) -> Option<String> {
    m.get(k).and_then(|v| if v.is_string() { Some(v.clone().cast::<String>()) } else { None })
}

fn get_int(m: &Map, k: &str) -> Option<i64> {
    m.get(k).and_then(|v| v.as_int().ok())
}

fn get_string_array(m: &Map, k: &str) -> Option<Vec<String>> {
    m.get(k).and_then(|v| {
        if v.is_array() {
            let arr = v.clone().cast::<Array>();
            let mut out = vec![];
            for it in arr {
                if it.is_string() {
                    out.push(it.cast::<String>());
                }
            }
            Some(out)
        } else {
            None
        }
    })
}

// Rhai-exposed functions

pub fn cloudhv_vm_create(spec: Map) -> Result<String, Box<EvalAltResult>> {
    let s = map_to_vmspec(spec)?;
    hv_to_rhai(cloudhv::vm_create(&s))
}

pub fn cloudhv_vm_start(id: &str) -> Result<(), Box<EvalAltResult>> {
    hv_to_rhai(cloudhv::vm_start(id))
}

pub fn cloudhv_vm_stop(id: &str, force: bool) -> Result<(), Box<EvalAltResult>> {
    hv_to_rhai(cloudhv::vm_stop(id, force))
}

pub fn cloudhv_vm_delete(id: &str, delete_disks: bool) -> Result<(), Box<EvalAltResult>> {
    hv_to_rhai(cloudhv::vm_delete(id, delete_disks))
}

pub fn cloudhv_vm_list() -> Result<Array, Box<EvalAltResult>> {
    let vms = hv_to_rhai(cloudhv::vm_list())?;
    let mut arr = Array::new();
    for rec in vms {
        arr.push(vmrecord_to_map(&rec).into());
    }
    Ok(arr)
}

pub fn cloudhv_vm_info(id: &str) -> Result<Map, Box<EvalAltResult>> {
    let rec = hv_to_rhai(cloudhv::vm_info(id))?;
    Ok(vmrecord_to_map(&rec))
}

// Module registration

pub fn register_cloudhv_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
    engine.register_fn("cloudhv_vm_create", cloudhv_vm_create);
    engine.register_fn("cloudhv_vm_start", cloudhv_vm_start);
    engine.register_fn("cloudhv_vm_stop", cloudhv_vm_stop);
    engine.register_fn("cloudhv_vm_delete", cloudhv_vm_delete);
    engine.register_fn("cloudhv_vm_list", cloudhv_vm_list);
    engine.register_fn("cloudhv_vm_info", cloudhv_vm_info);
    Ok(())
}