use std::path::{Component, PathBuf};
use anyhow::{anyhow, Context as _};
use reedline_repl_rs::clap::ArgMatches;
use s_macro::s;
pub use wildland_cargo_lib::api::CargoLib;
use wildland_corex::dfs::interface::node_stat::NodeType;
use wildland_corex::dfs::interface::{
    AbortFlag,
    DfsFrontendError,
    DummyIStream,
    DummyProgressReporter,
};
use crate::plugins::dfs::stream::{DevShellIStream, DevShellOStream, DevShellProgressReporter};
use crate::sigint::Ctrlc;
use crate::Context;
#[tracing::instrument(level = "trace", ret, err(Debug))]
pub fn aux_resolve_path(path: &String, cwd: PathBuf) -> anyhow::Result<String> {
    tracing::trace!("working with {}", path);
    let path = PathBuf::from(path);
    let mut components = path.components().peekable();
    let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
        components.next();
        PathBuf::from(c.as_os_str())
    } else {
        PathBuf::new()
    };
    if path.is_relative() {
        ret = cwd.join(ret);
    }
    for component in components {
        match component {
            Component::Prefix(..) => unreachable!(),
            Component::RootDir => {
                ret.push(component.as_os_str());
            }
            Component::CurDir => {}
            Component::ParentDir => {
                ret.pop();
            }
            Component::Normal(c) => {
                ret.push(c);
            }
        }
    }
    Ok(ret.to_str().context("Failed to serialize path")?.to_owned())
}
#[tracing::instrument(level = "trace", err(Debug), ret, skip(ctx))]
pub fn aux_dfs_get_path_type(ctx: &mut Context, path: &String) -> anyhow::Result<NodeType> {
    let path = aux_resolve_path(path, ctx.current_path.clone())?;
    let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
    let node = dfsapi
        .metadata(path.clone())
        .context(s!("path does not exist: {}", path))?;
    Ok(node.node_type)
}
#[tracing::instrument(level = "trace", ret, err(Debug))]
pub fn aux_size_mod(size: usize) -> anyhow::Result<String> {
    let mut size = size;
    let mut modifier = " b";
    if size > 1024 {
        size /= 1024;
        modifier = " Kib";
    }
    if size > 1024 {
        size /= 1024;
        modifier = " Mib";
    }
    if size > 1024 {
        size /= 1024;
        modifier = " Gib";
    }
    if size > 1024 {
        size /= 1024;
        modifier = " Tib";
    }
    Ok(format!("{size}{modifier}"))
}
#[tracing::instrument(level = "trace", ret, err(Debug))]
pub fn aux_resolve_local(path: &String, cwd: PathBuf) -> anyhow::Result<String> {
    let homedir = std::env::var("HOME").context("can not find HOME environment variable")?;
    let cwd = PathBuf::from("/");
    if path.matches('~').count() > 1 {
        return Err(anyhow!("`~` can only be used only once"));
    }
    if path.starts_with("~/") {
        let mut path = path.clone();
        path.replace_range(0..1, &homedir);
        aux_resolve_path(&path, cwd)
    } else if path.contains('~') {
        Err(anyhow!("`~` can only be used at the beginning of the path"))
    } else {
        aux_resolve_path(path, cwd)
    }
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_touch(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let path = args
        .get_one::<String>("path")
        .context("path not provided")?;
    let path = aux_resolve_path(path, ctx.current_path.clone())?;
    let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
    dfsapi
        .upload(
            path.clone(),
            DummyIStream::boxed(vec![]),
            Box::new(DummyProgressReporter {}),
            &AbortFlag::new(),
            None,
        )
        .context("Failed to create file")?;
    Ok(Some(s!("Ok: File Created: {}", path)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_mkdir(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let path = args
        .get_one::<String>("path")
        .context("path not provided")?;
    let path = aux_resolve_path(path, ctx.current_path.clone())?;
    tracing::debug!("creating dir with path {path}");
    let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
    dfsapi.create_dir(path).context("Failed to create file")?;
    Ok(Some(s!("Ok: Directory Created")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_rmdir(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let path = args
        .get_one::<String>("path")
        .context("path not provided")?;
    let is_recursive = args
        .get_one::<bool>("recursive")
        .unwrap_or(&false)
        .to_owned();
    let path = aux_resolve_path(path, ctx.current_path.clone())?;
    let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
    dfsapi
        .remove_dir(path, is_recursive)
        .context("Failed to remove directory")?;
    Ok(Some(s!("Ok: Directory Removed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_rm(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let path = args
        .get_one::<String>("path")
        .context("path not provided")?;
    let path = aux_resolve_path(path, ctx.current_path.clone())?;
    let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
    dfsapi.remove_file(path)?;
    Ok(Some(s!("Ok: File Removed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_stat(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let path = args
        .get_one::<String>("path")
        .context("path not provided")?;
    let path = aux_resolve_path(path, ctx.current_path.clone())?;
    let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
    let meta = dfsapi.metadata(path.clone())?;
    println!(
        "file:\t{},\nuuid:\t{}\nsize\t{}\n",
        path, meta.wildland_object_id, meta.size
    );
    Ok(Some(s!("Ok: File statistics printed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_cd(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let path = args
        .get_one::<String>("path")
        .context("path not provided")?;
    let path = aux_resolve_path(path, ctx.current_path.clone())?;
    if aux_dfs_get_path_type(ctx, &path)? != NodeType::Dir {
        return Err(anyhow::anyhow!("path is not a directory"));
    }
    ctx.current_path = std::path::PathBuf::from(path);
    Ok(Some(s!("")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_ls(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let path = match args.get_one::<String>("path") {
        Some(p) => p.to_owned(),
        None => ctx.current_path.clone().to_string_lossy().into(),
    };
    let path = aux_resolve_path(&path, ctx.current_path.clone())?;
    let dfs_api = ctx.get_dfs().context("can't get dfs api")?;
    let entries = dfs_api.read_dir(path).context("Failed to read directory")?;
    for entry in entries {
        let meta = entry.stat;
        let size = aux_size_mod(meta.size).unwrap_or(s!("???"));
        let modifier = match meta.node_type {
            NodeType::File => "-",
            NodeType::Dir => "d",
            NodeType::Other => "?",
            NodeType::Symlink => "l",
        };
        let perms = match meta.permissions.is_readonly() {
            true => "r-",
            false => "rw",
        };
        let name = PathBuf::from(entry.item_name)
            .file_name()
            .unwrap()
            .to_str()
            .unwrap()
            .to_owned();
        println!(" {modifier} {perms} \t{size: <9}\t{name}");
    }
    Ok(Some(s!("Ok: Directory listed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_get_path(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let uuid = args
        .get_one::<String>("uuid")
        .context("uuid not provided")?;
    let dfs_api = ctx.get_dfs().context("can't get dfs api")?;
    let entries = dfs_api.get_path(uuid.to_owned())?;
    println!("entries: {:?}", entries);
    Ok(Some(s!("Ok: uuid processed, paths printed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(_args, ctx))]
pub(crate) fn h_pwd(_args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    Ok(Some(s!(ctx
        .current_path
        .clone()
        .into_os_string()
        .into_string()
        .map_err(|_| anyhow!(
            "could not transform path to string"
        ))?)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_rename(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let lpath = args
        .get_one::<String>("source_path")
        .context("source path not provided")?;
    let lpath = aux_resolve_path(lpath, ctx.current_path.clone())?;
    let rpath = args
        .get_one::<String>("target_path")
        .context("target path not provided")?;
    let rpath = aux_resolve_path(rpath, ctx.current_path.clone())?;
    let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
    dfsapi
        .rename(lpath, rpath)
        .context("Failed to rename directory")?;
    Ok(Some(s!("Ok: Directory renamed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_get(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let source_path = args
        .get_one::<String>("source_path")
        .context("path not provided")?;
    let source_path = aux_resolve_path(source_path, ctx.current_path.clone())?;
    let target_path = args
        .get_one::<String>("target_path")
        .context("remote path not provided")?;
    let dfs_api = ctx.get_dfs().context("can't get dfs api")?;
    let ostream = Box::new(DevShellOStream::try_new(target_path)?);
    let abort_flag = AbortFlag::new();
    let _ctrlc = Ctrlc::new({
        let flag = abort_flag.clone();
        move || flag.set()
    });
    match dfs_api.download(
        source_path.clone(),
        ostream,
        Box::new(DevShellProgressReporter {}),
        &abort_flag,
    ) {
        Ok(_) => Ok(Some(format!(
            "File {source_path} downloaded to {target_path}"
        ))),
        Err(DfsFrontendError::Aborted) => Ok(Some("Downloading canceled".into())),
        Err(e) => Err(e.into()),
    }
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_put(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
    let source_path = args
        .get_one::<String>("source_path")
        .context("local path not provided")?;
    let target_path = args
        .get_one::<String>("target_path")
        .context("remote path not provided")?;
    let target_path = aux_resolve_path(target_path, ctx.current_path.clone())?;
    if !PathBuf::from(source_path.clone()).exists() {
        return Err(anyhow::anyhow!("local path does not exist: {source_path}"));
    }
    let dfs_api = ctx.get_dfs().context("can't get dfs api")?;
    let istream = Box::new(DevShellIStream::try_new(source_path)?);
    let abort_flag = AbortFlag::new();
    let _ctrlc = Ctrlc::new({
        let flag = abort_flag.clone();
        move || flag.set()
    });
    match dfs_api.upload(
        target_path.clone(),
        istream,
        Box::new(DevShellProgressReporter {}),
        &abort_flag,
        None,
    ) {
        Ok(_) => Ok(Some(format!(
            "File {source_path} uploaded as {target_path}"
        ))),
        Err(DfsFrontendError::Aborted) => Ok(Some("Uploading canceled".into())),
        Err(e) => Err(e.into()),
    }
}