//! Local device key storage and git signing configuration.
//!
//! Keys live under `~/.config/relicario/devices/<device-name>/`:
//!   signing.key   — ed25519 private key (OpenSSH, 0600)
//!   signing.pub   — ed25519 public key (OpenSSH single line)
//!   deploy.key    — ed25519 private key for git push (OpenSSH, 0600)
//!   deploy.pub    — ed25519 public key registered as Gitea deploy key
//!   gitea_key_id  — numeric Gitea deploy key ID for later revocation
//!
//! The file `~/.config/relicario/devices/current` holds the active device name
//! (one plain-text line).

use std::fs::{self, Permissions};
use std::path::PathBuf;

use anyhow::{Context, Result};
use zeroize::Zeroizing;

/// `~/.config/relicario/devices/`
pub fn devices_dir() -> Result<PathBuf> {
    let config = dirs::config_dir()
        .ok_or_else(|| anyhow::anyhow!("no config directory available"))?;
    Ok(config.join("relicario").join("devices"))
}

/// `~/.config/relicario/devices/<name>/`
pub fn device_dir(name: &str) -> Result<PathBuf> {
    Ok(devices_dir()?.join(name))
}

/// Read the current device name from `devices/current`, or `None` if not set.
pub fn current_device() -> Result<Option<String>> {
    let path = devices_dir()?.join("current");
    if !path.exists() {
        return Ok(None);
    }
    let name = fs::read_to_string(&path)
        .context("read current device")?
        .trim()
        .to_string();
    if name.is_empty() {
        Ok(None)
    } else {
        Ok(Some(name))
    }
}

/// Write the active device name to `devices/current`.
pub fn set_current_device(name: &str) -> Result<()> {
    let dir = devices_dir()?;
    fs::create_dir_all(&dir).context("create devices dir")?;
    fs::write(dir.join("current"), format!("{name}\n"))
        .context("write current device")?;
    Ok(())
}

/// Store all keys for a device, applying restrictive permissions on private
/// key files on Unix.
pub fn store_device_keys(
    name: &str,
    signing_private: &str,
    signing_public: &str,
    deploy_private: &str,
    deploy_public: &str,
    gitea_key_id: u64,
) -> Result<()> {
    let dir = device_dir(name)?;
    fs::create_dir_all(&dir).context("create device dir")?;

    fs::write(dir.join("signing.key"), signing_private)
        .context("write signing.key")?;
    fs::write(dir.join("signing.pub"), signing_public)
        .context("write signing.pub")?;
    fs::write(dir.join("deploy.key"), deploy_private)
        .context("write deploy.key")?;
    fs::write(dir.join("deploy.pub"), deploy_public)
        .context("write deploy.pub")?;
    fs::write(dir.join("gitea_key_id"), gitea_key_id.to_string())
        .context("write gitea_key_id")?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        fs::set_permissions(dir.join("signing.key"), Permissions::from_mode(0o600))
            .context("chmod signing.key")?;
        fs::set_permissions(dir.join("deploy.key"), Permissions::from_mode(0o600))
            .context("chmod deploy.key")?;
    }

    Ok(())
}

/// Load the signing private key for a device.
pub fn load_signing_key(name: &str) -> Result<Zeroizing<String>> {
    let path = device_dir(name)?.join("signing.key");
    let key = fs::read_to_string(&path)
        .with_context(|| format!("read signing key for device '{name}'"))?;
    Ok(Zeroizing::new(key))
}

/// Load the deploy private key for a device.
pub fn load_deploy_key(name: &str) -> Result<Zeroizing<String>> {
    let path = device_dir(name)?.join("deploy.key");
    let key = fs::read_to_string(&path)
        .with_context(|| format!("read deploy key for device '{name}'"))?;
    Ok(Zeroizing::new(key))
}

/// Load the Gitea deploy key ID for a device.
pub fn load_gitea_key_id(name: &str) -> Result<u64> {
    let path = device_dir(name)?.join("gitea_key_id");
    let id_str = fs::read_to_string(&path)
        .with_context(|| format!("read Gitea key ID for device '{name}'"))?;
    id_str.trim().parse().context("parse Gitea key ID")
}

/// Delete the local key directory for a device.
pub fn delete_device_keys(name: &str) -> Result<()> {
    let dir = device_dir(name)?;
    if dir.exists() {
        fs::remove_dir_all(&dir)
            .with_context(|| format!("delete device dir for '{name}'"))?;
    }
    Ok(())
}

/// Configure git in `vault_root` to:
///  - sign commits with the device's signing key (SSH format)
///  - push via SSH using the device's deploy key
pub fn configure_git_signing(vault_root: &std::path::Path, name: &str) -> Result<()> {
    let dir = device_dir(name)?;
    let signing_key = dir.join("signing.key");
    let deploy_key = dir.join("deploy.key");

    // gpg.format = ssh so git uses SSH-format signing
    crate::helpers::git_command(vault_root, &["config", "gpg.format", "ssh"])
        .status()
        .context("git config gpg.format")?;

    // user.signingkey = path to the private key file
    crate::helpers::git_command(
        vault_root,
        &["config", "user.signingkey", &signing_key.to_string_lossy()],
    )
    .status()
    .context("git config user.signingkey")?;

    // commit.gpgsign = true
    crate::helpers::git_command(vault_root, &["config", "commit.gpgsign", "true"])
        .status()
        .context("git config commit.gpgsign")?;

    // core.sshCommand — use only the deploy key for push
    let ssh_cmd = format!(
        "ssh -i {} -o IdentitiesOnly=yes",
        deploy_key.display()
    );
    crate::helpers::git_command(
        vault_root,
        &["config", "core.sshCommand", &ssh_cmd],
    )
    .status()
    .context("git config core.sshCommand")?;

    Ok(())
}
