use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;

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

/// Path to the Claude Code config file.
pub fn claude_config_path() -> PathBuf {
    dirs::home_dir()
        .expect("cannot find home directory")
        .join(".claude.json")
}

/// Read the full ~/.claude.json as a JSON value.
pub fn read_claude_config() -> Result<serde_json::Value> {
    let path = claude_config_path();
    let data = fs::read_to_string(&path)
        .with_context(|| format!("failed to read {}", path.display()))?;
    let value: serde_json::Value = serde_json::from_str(&data)
        .with_context(|| format!("failed to parse {} as JSON", path.display()))?;
    Ok(value)
}

/// Write the full config back to ~/.claude.json atomically via a temp file.
/// Never call this without having first read the config — always merge, never replace.
pub fn write_claude_config(config: &serde_json::Value) -> Result<()> {
    let path = claude_config_path();
    let data = serde_json::to_string_pretty(config)?;

    // Write to a sibling temp file, then rename atomically.
    let tmp_path = path.with_extension("json.tmp");
    fs::write(&tmp_path, &data)
        .with_context(|| format!("failed to write temp file {}", tmp_path.display()))?;
    fs::rename(&tmp_path, &path)
        .with_context(|| format!("failed to rename {} -> {}", tmp_path.display(), path.display()))?;

    // Keep permissions tight.
    fs::set_permissions(&path, fs::Permissions::from_mode(0o600))
        .with_context(|| format!("failed to set permissions on {}", path.display()))?;

    Ok(())
}

/// Extract the oauthAccount field from a parsed config.
pub fn extract_oauth_account(config: &serde_json::Value) -> Result<serde_json::Value> {
    config
        .get("oauthAccount")
        .cloned()
        .ok_or_else(|| anyhow!(
            "oauthAccount field not found in ~/.claude.json\n\
             Make sure you are logged in to Claude Code."
        ))
}

/// Extract the email address from an oauthAccount value.
/// Tries multiple common field names.
pub fn extract_email(oauth: &serde_json::Value) -> Result<String> {
    oauth
        .get("emailAddress")
        .or_else(|| oauth.get("email"))
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .ok_or_else(|| anyhow!(
            "could not find email in oauthAccount (tried 'emailAddress', 'email')"
        ))
}

/// Extract the UUID/ID from an oauthAccount value.
/// Tries multiple common field names.
pub fn extract_uuid(oauth: &serde_json::Value) -> Result<String> {
    oauth
        .get("id")
        .or_else(|| oauth.get("uuid"))
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .ok_or_else(|| anyhow!(
            "could not find id/uuid in oauthAccount (tried 'id', 'uuid')"
        ))
}

/// Inject an oauthAccount value into an existing config, replacing only that field.
/// All other fields in ~/.claude.json are left untouched.
pub fn inject_oauth_account(
    config: &mut serde_json::Value,
    oauth: &serde_json::Value,
) -> Result<()> {
    let obj = config
        .as_object_mut()
        .ok_or_else(|| anyhow!("~/.claude.json is not a JSON object"))?;
    obj.insert("oauthAccount".to_string(), oauth.clone());
    Ok(())
}
