use std::path::{Path, PathBuf};
use anyhow::{anyhow, Context as _};
use reedline_repl_rs::clap::{Arg, ArgMatches, Command};
use reedline_repl_rs::Repl;
use s_macro::s;
use wildland_cargo_lib::api::container::{CargoContainer, MountState};
use wildland_cargo_lib::api::CargoLib;
use wildland_corex::dfs::interface::SpaceUsage;
use wildland_corex::StorageTemplate;
use wildland_storage_backend::lfs::template::LocalFilesystemStorageTemplate;
use crate::{match_context, Context};
#[tracing::instrument(level = "trace", err(Debug), skip(cargo))]
pub fn aux_load_template_file(cargo: &CargoLib, file: &Path) -> anyhow::Result<StorageTemplate> {
    if !file.exists() || !file.is_file() {
        return Err(anyhow::anyhow!(
            "File {} does not exist or is not a file",
            file.to_str().unwrap()
        ));
    }
    let template_payload = std::fs::read_to_string(file)?.into_bytes();
    cargo
        .storage_template_from_json(template_payload)
        .map_err(Into::into)
}
#[tracing::instrument(skip(container), ret)]
pub fn aux_container_info(container: &CargoContainer) -> String {
    let mut out = format!(
        r#"
    container info:
        name: {}
        uuid: {}
        path: {}
        storages:
    "#,
        container.name().unwrap_or(s!("?no-name?")),
        container.uuid(),
        container.get_path().unwrap_or_default()
    );
    if let Ok(storages) = container.clone().get_storages() {
        for s in storages {
            let data = String::from_utf8(s.data()).unwrap_or("null".into());
            let space_usage = s.get_space_usage().unwrap_or(SpaceUsage::new(0u64, 0u64));
            let template_uuid = match s.template_uuid() {
                Some(template_uuid) => template_uuid.to_string(),
                None => s!("None"),
            };
            let storage_uuid = s.uuid().to_string();
            let backend_type = s.backend_type();
            let name = s.name().unwrap_or("error".into());
            out.push_str(&format!(
                "
\t >> used_space: {space_usage}
\t >> template uuid: {template_uuid}
\t >> storage uuid: {storage_uuid}
\t >> backend type: {backend_type}
\t >> name: {name}
\t >> data: {data}
"
            ));
        }
    }
    out
}
#[tracing::instrument(level = "trace", err(Debug), err(Debug), skip(_args, context))]
fn h_ct_list(_args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
    let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
    let containers = cargo_user
        .find_containers(None, MountState::MountedOrUnmounted)
        .context(anyhow!("failed to list containers"))?;
    let containers_len = containers.len();
    let container_list = containers.iter().fold(String::new(), |mut acc, c| {
        acc += &aux_container_info(c);
        acc
    });
    let output = format!(
        r#"
Containers ({containers_len} found):
{container_list}
"#
    );
    Ok(Some(output))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_create_from_file(
    args: ArgMatches,
    context: &mut Context,
) -> anyhow::Result<Option<String>> {
    let (cargo, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
    let path = args
        .get_one::<String>("claim")
        .context(anyhow!("claim path not provided"))?;
    let name = args
        .get_one::<String>("name")
        .context(anyhow!("name not provided"))?;
    let template_file = args
        .get_one::<String>("template_file")
        .context(anyhow!("template filename not provided"))?;
    let is_encrypted = args
        .get_one::<bool>("encrypted")
        .unwrap_or(&false)
        .to_owned();
    let templatefile = PathBuf::from(template_file);
    let cargo_template = aux_load_template_file(cargo, &templatefile)?;
    let cthandle =
        cargo_user.create_container(s!(name), &cargo_template, s!(path), is_encrypted)?;
    Ok(Some(aux_container_info(&cthandle)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_create_lfs(args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
    let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
    let path = args
        .get_one::<String>("claim")
        .context("path not provided")?;
    let name = args
        .get_one::<String>("name")
        .context("name not provided")?;
    let local_path = args
        .get_one::<String>("path")
        .context("where locally to bound contaiener to the FS")?;
    let is_encrypted = args
        .get_one::<bool>("encrypted")
        .unwrap_or(&false)
        .to_owned();
    let local_path = PathBuf::from(local_path);
    if !local_path.exists() || !local_path.is_dir() {
        return Err(anyhow!("local directory does not seems to exist"));
    }
    let cargo_template = LocalFilesystemStorageTemplate::new(local_path.clone());
    let cargo_template = StorageTemplate::try_from(cargo_template)?;
    let cthandle =
        cargo_user.create_container(s!(name), &cargo_template, s!(path), is_encrypted)?;
    let uuid = cthandle.uuid();
    let local_path_full = local_path.join(name).join(uuid.to_string());
    std::fs::create_dir_all(local_path_full).context("container created but i failed to create local directory. File oprations referencing this container may fail")?;
    Ok(Some(aux_container_info(&cthandle)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_mount(args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
    let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
    let name_or_uuid = args
        .get_one::<String>("name-or-uuid")
        .context("path/uuid not provided")?;
    let containers = cargo_user
        .find_containers(None, MountState::Unmounted)
        .context(anyhow!("failed to find eligible containers"))?;
    let container = if let Ok(uuid) = uuid::Uuid::parse_str(name_or_uuid) {
        tracing::debug!("trying to find container by uuid");
        println!("trying to find container by uuid");
        containers
            .iter()
            .find(|c| c.uuid() == uuid)
            .context("container not found or is already mounted")?
    } else {
        tracing::debug!("failed to parse param as an uuid, trying to find container by name");
        println!("failed to parse param as an uuid, trying to find container by name");
        containers
            .iter()
            .find(|c| c.name() == Ok(name_or_uuid.clone()))
            .context("container not found or is already mounted")?
    };
    let container_info = aux_container_info(container);
    container
        .mount(None)
        .context(s!("failed to mount container: {}", container_info))?;
    Ok(Some(s!("mounted container: {}", container_info)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_remove(args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
    let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
    let name_or_uuid = args
        .get_one::<String>("name-or-uuid")
        .context("path/uuid not provided")?;
    let containers = cargo_user
        .find_containers(None, MountState::Unmounted)
        .context(anyhow!("failed to find eligible containers"))?;
    let container = if let Ok(uuid) = uuid::Uuid::parse_str(name_or_uuid) {
        tracing::debug!("trying to find container by uuid");
        containers
            .iter()
            .find(|c| c.uuid() == uuid)
            .context("container not found or is mounted")?
    } else {
        println!("failed to parse param as an uuid, trying to find container by name");
        containers
            .iter()
            .find(|c| c.name() == Ok(name_or_uuid.clone()))
            .context("container not found or is mounted")?
    };
    let container_info = aux_container_info(container);
    container
        .remove()
        .context(s!("failed to remove container: {}", container_info))?;
    Ok(Some(s!("removed container {}", container_info)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, context))]
fn h_ct_unmount(args: ArgMatches, context: &mut Context) -> anyhow::Result<Option<String>> {
    let (_, cargo_user) = match_context!(context, cargo => Some(_), cargo_user => Some(_))?;
    let name_or_uuid = args
        .get_one::<String>("name-or-uuid")
        .context("path/uuid not provided")?;
    let containers = cargo_user
        .find_containers(None, MountState::Mounted)
        .context(anyhow!("failed to find eligible containers"))?;
    let container = if let Ok(uuid) = uuid::Uuid::parse_str(name_or_uuid) {
        tracing::debug!("trying to find container by uuid");
        println!("trying to find container by uuid");
        containers
            .iter()
            .find(|c| c.uuid() == uuid)
            .context("container not found or is already mounted")?
    } else {
        tracing::debug!("failed to parse param as an uuid, trying to find container by name");
        println!("failed to parse param as an uuid, trying to find container by name");
        containers
            .iter()
            .find(|c| c.name() == Ok(name_or_uuid.clone()))
            .context("container not found or is already mounted")?
    };
    let container_info = aux_container_info(container);
    container
        .unmount(None)
        .context(s!("failed to unmount container: {}", container_info))?;
    Ok(Some(s!("unmount container: {}", container_info)))
}
pub fn extend(repl: Repl<Context, anyhow::Error>) -> Repl<Context, anyhow::Error> {
    repl.with_command(
        Command::new("ct-new-file")
            .about("container->create new from file template")
            .arg(Arg::new("name").required(true).index(1))
            .arg(Arg::new("claim").required(true).index(2))
            .arg(Arg::new("template_file").required(true).index(3))
            .arg(
                Arg::new("encrypted")
                    .short('e')
                    .help("encrypt storage")
                    .action(clap::ArgAction::SetTrue),
            ),
        h_ct_create_from_file,
    )
    .with_command(
        Command::new("ct-new-lfs")
            .about("container->create new local FS storage")
            .arg(Arg::new("name").required(true).index(1))
            .arg(Arg::new("claim").required(true).index(2))
            .arg(Arg::new("path").required(true).index(3))
            .arg(
                Arg::new("encrypted")
                    .short('e')
                    .help("encrypt storage")
                    .action(clap::ArgAction::SetTrue),
            ),
        h_ct_create_lfs,
    )
    .with_command(
        Command::new("ct-mount")
            .about("container->mount by name or uuid")
            .arg(Arg::new("name-or-uuid").required(true).index(1)),
        h_ct_mount,
    )
    .with_command(
        Command::new("ct-unmount")
            .about("container->unmount")
            .arg(Arg::new("name-or-uuid").required(true).index(1)),
        h_ct_unmount,
    )
    .with_command(
        Command::new("ct-list").about("container->list containers"),
        h_ct_list,
    )
    .with_command(
        Command::new("ct-remove")
            .about("container->remove containers")
            .arg(Arg::new("name-or-uuid").required(true).index(1)),
        h_ct_remove,
    )
}