// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use std::{
    env,
    path::{Path, PathBuf},
};

// Similar to fs::canonicalize, but ignores UNC paths and returns the path as is (for windows).
// Usefulfor windows to ensure we have the paths in the right casing.
// For unix, this is a noop.
pub fn norm_case<P: AsRef<Path>>(path: P) -> PathBuf {
    // On unix do not use canonicalize, results in weird issues with homebrew paths
    // Even readlink does the same thing
    // Running readlink for a path thats not a symlink ends up returning relative paths for some reason.
    // A better solution is to first check if a path is a symlink and then resolve it.
    #[cfg(unix)]
    return path.as_ref().to_path_buf();

    #[cfg(windows)]
    use std::fs;

    #[cfg(windows)]
    if let Ok(resolved) = fs::canonicalize(&path) {
        if cfg!(unix) {
            return resolved;
        }
        // Windows specific handling, https://github.com/rust-lang/rust/issues/42869
        let has_unc_prefix = path.as_ref().to_string_lossy().starts_with(r"\\?\");
        if resolved.to_string_lossy().starts_with(r"\\?\") && !has_unc_prefix {
            // If the resolved path has a UNC prefix, but the original path did not,
            // we need to remove the UNC prefix.
            PathBuf::from(resolved.to_string_lossy().trim_start_matches(r"\\?\"))
        } else {
            resolved
        }
    } else {
        path.as_ref().to_path_buf()
    }
}

// Resolves symlinks to the real file.
// If the real file == exe, then it is not a symlink.
pub fn resolve_symlink<T: AsRef<Path>>(exe: &T) -> Option<PathBuf> {
    let name = exe.as_ref().file_name()?.to_string_lossy();
    // In bin directory of homebrew, we have files like python-build, python-config, python3-config
    if name.ends_with("-config") || name.ends_with("-build") {
        return None;
    }
    // We support resolving conda symlinks.
    if !name.starts_with("python") && !name.starts_with("conda") {
        return None;
    }

    // Running readlink for a path thats not a symlink ends up returning relative paths for some reason.
    // A better solution is to first check if a path is a symlink and then resolve it.
    let metadata = std::fs::symlink_metadata(exe).ok()?;
    if metadata.is_file() || !metadata.file_type().is_symlink() {
        return None;
    }
    if let Ok(readlink) = std::fs::canonicalize(exe) {
        if readlink == exe.as_ref().to_path_buf() {
            None
        } else {
            Some(readlink)
        }
    } else {
        None
    }
}

pub fn expand_path(path: PathBuf) -> PathBuf {
    if path.starts_with("~") {
        if let Some(ref home) = get_user_home() {
            if let Ok(path) = path.strip_prefix("~") {
                return home.join(path);
            } else {
                return path;
            }
        }
    }

    // Specifically for https://docs.conda.io/projects/conda/en/23.1.x/user-guide/configuration/use-condarc.html#expansion-of-environment-variables
    if path.to_str().unwrap_or_default().contains("${USERNAME}")
        || path.to_str().unwrap_or_default().contains("${HOME}")
    {
        let username = env::var("USERNAME")
            .or(env::var("USER"))
            .unwrap_or_default();
        let home = env::var("HOME")
            .or_else(|_| env::var("USERPROFILE"))
            .unwrap_or_default();
        return PathBuf::from(
            path.to_str()
                .unwrap()
                .replace("${USERNAME}", &username)
                .replace("${HOME}", &home),
        );
    }
    path
}

fn get_user_home() -> Option<PathBuf> {
    let home = env::var("HOME").or_else(|_| env::var("USERPROFILE"));
    match home {
        Ok(home) => Some(norm_case(PathBuf::from(home))),
        Err(_) => None,
    }
}
