use std::path::Path;

use cargo_metadata::camino::Utf8PathBuf;
use tracing::debug;

use crate::error::Error;

fn utf8_path_replace(path: &Utf8PathBuf, left: &str, right: &str) -> Utf8PathBuf {
    Utf8PathBuf::from(path.to_string().replace(left, right))
}

#[derive(Clone, Debug, PartialEq)]
pub struct Metadata {
    pub inner: cargo_metadata::Package,
}

impl Metadata {
    pub fn from_cargo_metadata<P: AsRef<Path>>(
        mut md: cargo_metadata::Package,
        root: P,
        path_in_vcs: Option<&str>,
    ) -> Self {
        let root_str = {
            let mut temp = root.as_ref().to_path_buf();
            if let Some(path_in_vcs) = path_in_vcs {
                temp.push(path_in_vcs);
            }
            temp.push("");
            temp.to_string_lossy().to_string()
        };

        // manifest_path: replace absolute path
        md.manifest_path = utf8_path_replace(&md.manifest_path, &root_str, "");

        for target in &mut md.targets {
            // src_path: replace absolute path
            target.src_path = utf8_path_replace(&target.src_path, &root_str, "");
        }

        Metadata { inner: md }
    }
}

pub(crate) fn load_metadata_from_path<P: AsRef<Path>>(path: P) -> Result<cargo_metadata::Metadata, Error> {
    debug!(
        "Loading cargo metadata from directory: {}",
        path.as_ref().to_string_lossy()
    );

    cargo_metadata::MetadataCommand::new()
        .manifest_path(path.as_ref())
        .no_deps()
        .other_options(vec![String::from("--offline")])
        .exec()
        .map_err(|err| Error::Metadata { inner: err.to_string() })
}

pub fn get_crate_metadata<P: AsRef<Path>>(root: P) -> Result<Metadata, Error> {
    let path = root.as_ref().join("Cargo.toml");
    let md = load_metadata_from_path(path)?;

    let [package]: [cargo_metadata::Package; 1] =
        md.packages
            .into_iter()
            .collect::<Vec<_>>()
            .try_into()
            .map_err(|_| Error::Metadata {
                inner: String::from("Failed to load crate metadata (too many workspace members)."),
            })?;

    Ok(Metadata::from_cargo_metadata(package, root, None))
}

pub fn get_crate_metadata_from_workspace<P: AsRef<Path>>(
    root: P,
    path_in_vcs: &str,
    name: &str,
) -> Result<Metadata, Error> {
    let path = root.as_ref().join(path_in_vcs).join("Cargo.toml");
    let md = load_metadata_from_path(path)?;

    let [package]: [cargo_metadata::Package; 1] = md
        .packages
        .into_iter()
        .filter(|p| p.name.as_ref() == name)
        .collect::<Vec<_>>()
        .try_into()
        .map_err(|_| Error::Metadata {
            inner: format!(
                "Failed to load crate metadata (name does not match any single workspace member): {}",
                name.to_owned()
            ),
        })?;

    Ok(Metadata::from_cargo_metadata(package, root, Some(path_in_vcs)))
}
