use std::{
    fs::{Permissions, ReadDir},
    io::{ErrorKind, Write},
    path::Path,
};

use anyhow::Context;

use crate::proc;

pub fn is_dir(p: &Path) -> anyhow::Result<bool> {
    log::trace!("is_dir: {}", p.display());
    Ok(match std::fs::metadata(p) {
        Ok(m) if m.is_dir() => true,
        Ok(_) => false,
        Err(e) if e.kind() == ErrorKind::NotFound => false,
        Err(e) => return Err(e).with_context(|| format!("Could not stat {p:?}")),
    })
}

pub fn is_file(p: &Path) -> anyhow::Result<bool> {
    log::trace!("is_file: {}", p.display());
    Ok(match std::fs::metadata(p) {
        Ok(m) if m.is_file() => true,
        Ok(_) => false,
        Err(e) if e.kind() == ErrorKind::NotFound => false,
        Err(e) => return Err(e).with_context(|| format!("Could not stat {p:?}")),
    })
}

pub fn is_file_with_permissions(p: &Path, permissions: Permissions) -> anyhow::Result<bool> {
    log::trace!("is_file_with_permissions: {}", p.display());
    Ok(match std::fs::metadata(p) {
        Ok(m) if m.is_file() => {
            log::trace!("Verifying {:?} == {:?}", m.permissions(), permissions);
            m.permissions() == permissions
        }
        Ok(_) => false,
        Err(e) if e.kind() == ErrorKind::NotFound => false,
        Err(e) => return Err(e).with_context(|| format!("Could not stat {p:?}")),
    })
}

pub fn create_dir(p: &Path) -> anyhow::Result<()> {
    log::info!("Creating directory : {}", p.display());
    std::fs::create_dir(p).with_context(|| format!("Could not create directory {p:?}"))
}

pub fn write_file_string(p: &Path, contents: &str, permissions: Permissions) -> anyhow::Result<()> {
    log::info!("Writing contents to {}", p.display());
    let mut file = std::fs::File::create(p).with_context(|| {
        format!(
            "Could not create (or open existing) file at {}",
            p.display()
        )
    })?;
    file.set_permissions(permissions)
        .with_context(|| format!("Could not set permissions on file {}", p.display()))?;
    file.write_all(contents.as_bytes())
        .with_context(|| format!("Could not write to file {}", p.display()))?;
    Ok(())
}

pub fn root_create_dir(p: &Path) -> anyhow::Result<()> {
    let mut cmd = proc::Command::new("mkdir");
    cmd.arg(p.display().to_string());

    let _ = cmd.sudo().try_spawn_to_string()?;

    Ok(())
}

pub fn remove_file(path: &Path) -> anyhow::Result<()> {
    log::info!("Deleting file {}", path.display());
    std::fs::remove_file(path).with_context(|| format!("Could not delete file {}", path.display()))
}

pub fn root_create_dir_recursive(p: &Path) -> anyhow::Result<()> {
    let mut cmd = proc::Command::new("mkdir");
    cmd.arg("-p");
    cmd.arg(p.display().to_string());

    let _ = cmd.sudo().try_spawn_to_string()?;

    Ok(())
}

pub fn create_dir_recursive(p: &Path) -> anyhow::Result<()> {
    let mut cmd = proc::Command::new("mkdir");
    cmd.arg("-p");
    cmd.arg(p.display().to_string());

    let _ = cmd.try_spawn_to_string()?;

    Ok(())
}

pub fn remove_dir_recursive(p: &Path) -> anyhow::Result<()> {
    std::fs::remove_dir_all(p)
        .with_context(|| format!("Could not remove directory {}", p.display()))
}

pub fn list_dir(p: &Path) -> anyhow::Result<ReadDir> {
    std::fs::read_dir(p).with_context(|| format!("Could not read directory {}", p.display()))
}

pub fn set_permissions(p: &Path, permissions: Permissions) -> anyhow::Result<()> {
    log::trace!("set_permissions: {}", p.display());
    std::fs::set_permissions(p, permissions.clone()).with_context(|| {
        format!(
            "Could not set permissions on {} to {permissions:?}",
            p.display()
        )
    })
}

pub fn metadata(p: &Path) -> anyhow::Result<std::fs::Metadata> {
    log::trace!("get_metadata: {}", p.display());
    std::fs::metadata(p).with_context(|| format!("Could not get metadata for {}", p.display()))
}

#[cfg(target_family = "unix")]
pub fn user_only_dir_permissions() -> Permissions {
    use std::os::unix::fs::PermissionsExt;

    PermissionsExt::from_mode(0o040700)
}

#[cfg(target_family = "unix")]
pub fn user_only_file_permissions() -> Permissions {
    use std::os::unix::fs::PermissionsExt;

    PermissionsExt::from_mode(0o100600)
}

pub fn read_to_string(path: &Path) -> anyhow::Result<String> {
    log::trace!("read_file: {}", path.display());
    std::fs::read_to_string(path)
        .with_context(|| format!("Could not read file: {}", path.display()))
}

#[cfg(target_family = "unix")]
pub fn create_link(from: &Path, to: &Path) -> anyhow::Result<()> {
    std::os::unix::fs::symlink(to, from).with_context(|| {
        format!(
            "Could not create symbolic link from {from} to {to}",
            from = from.display(),
            to = to.display()
        )
    })
}

pub fn rename(from: &Path, to: &Path) -> anyhow::Result<()> {
    std::fs::rename(from, to)
        .with_context(|| format!("Could not rename {} to {}", from.display(), to.display()))
}
