//! Node identity: persistent Ed25519 key for private identification.
//! Enables future P2P features (file transfer, streaming, ecash/Lightning).
//! Supports did:key (W3C) for Web5/DID interoperability.

use anyhow::{Context, Result};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use rand::rngs::OsRng;
use std::path::{Path, PathBuf};
use tokio::fs;

const NODE_KEY_FILE: &str = "node_key";
const NODE_KEY_PUB_FILE: &str = "node_key.pub";

/// Persistent node identity (Ed25519 keypair).
/// Survives reboots; used for signing, verification, and node address.
pub struct NodeIdentity {
    signing_key: SigningKey,
    identity_dir: PathBuf,
}

impl NodeIdentity {
    /// Load existing identity or create and persist a new one.
    pub async fn load_or_create(identity_dir: &Path) -> Result<Self> {
        fs::create_dir_all(identity_dir)
            .await
            .context("Failed to create identity directory")?;

        let key_path = identity_dir.join(NODE_KEY_FILE);
        let pub_path = identity_dir.join(NODE_KEY_PUB_FILE);

        let signing_key = if key_path.exists() {
            let bytes = fs::read(&key_path)
                .await
                .context("Failed to read node key")?;
            let arr: [u8; 32] = bytes
                .try_into()
                .map_err(|_| anyhow::anyhow!("Invalid node key length"))?;
            SigningKey::from_bytes(&arr)
        } else {
            let signing_key = SigningKey::generate(&mut OsRng);
            fs::write(&key_path, signing_key.to_bytes())
                .await
                .context("Failed to write node key")?;
            #[cfg(unix)]
            {
                use std::os::unix::fs::PermissionsExt;
                fs::set_permissions(&key_path, std::fs::Permissions::from_mode(0o600))
                    .await
                    .context("Failed to set key permissions")?;
            }
            fs::write(&pub_path, signing_key.verifying_key().as_bytes())
                .await
                .context("Failed to write node public key")?;
            tracing::info!("🔑 Generated new node identity at {}", identity_dir.display());
            signing_key
        };

        Ok(Self {
            signing_key,
            identity_dir: identity_dir.to_path_buf(),
        })
    }

    /// Public key as hex string (for ServerInfo, Nostr, etc.)
    pub fn pubkey_hex(&self) -> String {
        hex::encode(self.signing_key.verifying_key().as_bytes())
    }

    /// Stable node ID derived from pubkey (first 16 chars of hex).
    pub fn node_id(&self) -> String {
        self.pubkey_hex().chars().take(16).collect()
    }

    /// Sign data; returns hex-encoded signature.
    pub fn sign(&self, data: &[u8]) -> String {
        hex::encode(self.signing_key.sign(data).to_bytes())
    }

    /// Verify a signature from a peer (pubkey hex, data, signature hex).
    pub fn verify(pubkey_hex: &str, data: &[u8], sig_hex: &str) -> Result<bool> {
        let bytes = hex::decode(pubkey_hex).context("Invalid pubkey hex")?;
        let verifying_key = VerifyingKey::from_bytes(
            bytes
                .as_slice()
                .try_into()
                .map_err(|_| anyhow::anyhow!("Invalid pubkey length"))?,
        )?;
        let sig_bytes = hex::decode(sig_hex).context("Invalid signature hex")?;
        let sig = Signature::from_bytes(
            sig_bytes
                .as_slice()
                .try_into()
                .map_err(|_| anyhow::anyhow!("Invalid signature length"))?,
        );
        Ok(verifying_key.verify(data, &sig).is_ok())
    }

    /// Node address format for invites: archipelago://<onion>#<pubkey>
    pub fn node_address(&self, onion: &str) -> String {
        format!("archipelago://{}#{}", onion.trim_end_matches('/'), self.pubkey_hex())
    }

    /// DID in did:key format (W3C did:key method, Ed25519).
    /// Format: did:key:z&lt;base58btc(multicodec_ed25519_pub + 32-byte pubkey)&gt;
    pub fn did_key(&self) -> String {
        did_key_from_pubkey_hex(&self.pubkey_hex()).expect("pubkey_hex is valid")
    }
}

/// Convert Ed25519 pubkey (hex) to did:key format.
/// Used by RPC when identity is loaded from state.
pub fn did_key_from_pubkey_hex(pubkey_hex: &str) -> Result<String> {
    let bytes = hex::decode(pubkey_hex).context("Invalid pubkey hex")?;
    if bytes.len() != 32 {
        return Err(anyhow::anyhow!("Invalid pubkey length"));
    }
    let mut multicodec_pubkey = [0u8; 34];
    multicodec_pubkey[0] = 0xed;
    multicodec_pubkey[1] = 0x01;
    multicodec_pubkey[2..34].copy_from_slice(&bytes);
    Ok(format!("did:key:z{}", bs58::encode(multicodec_pubkey).into_string()))
}
