//! Cryptographic helpers for every.channel.

use chacha20poly1305::{aead::Aead, KeyInit, XChaCha20Poly1305, XNonce};
use ec_core::{ManifestBody, ManifestSignature};
use ec_eth::{manifest_body_eip712_signing_hash, ETH_MANIFEST_SIG_ALG};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use k256::ecdsa::{
    RecoveryId as SecpRecoveryId, Signature as SecpSignature, SigningKey as SecpSigningKey,
    VerifyingKey as SecpVerifyingKey,
};
use sha3::{Digest, Keccak256};
use std::env;
use std::fs;

pub const MANIFEST_SIG_ALG: &str = "ed25519";
pub const ETH_MANIFEST_SIGNING_KEY_ENV: &str = "EVERY_CHANNEL_ETH_MANIFEST_SIGNING_KEY";

pub const ENCRYPTION_ALG: &str = "xchacha20poly1305";

/// Derive a stream encryption key from a stream id and optional network secret.
///
/// This is deterministic: identical stream ids produce identical keys.
pub fn derive_stream_key(stream_id: &str, network_secret: Option<&[u8]>) -> [u8; 32] {
    let mut input = Vec::new();
    if let Some(secret) = network_secret {
        input.extend_from_slice(secret);
        input.push(0);
    }
    input.extend_from_slice(stream_id.as_bytes());

    blake3::derive_key("every.channel stream key v1", &input)
}

/// Derive a deterministic nonce for a stream chunk.
pub fn derive_stream_nonce(stream_id: &str, chunk_index: u64) -> [u8; 24] {
    let mut hasher = blake3::Hasher::new();
    hasher.update(b"every.channel stream nonce v1");
    hasher.update(stream_id.as_bytes());
    hasher.update(&chunk_index.to_be_bytes());
    let hash = hasher.finalize();
    let mut nonce = [0u8; 24];
    nonce.copy_from_slice(&hash.as_bytes()[..24]);
    nonce
}

#[derive(Debug, Clone)]
pub struct EncryptedPayload {
    pub ciphertext: Vec<u8>,
    pub nonce: [u8; 24],
    pub alg: &'static str,
}

pub fn encrypt_stream_data(
    stream_id: &str,
    chunk_index: u64,
    plaintext: &[u8],
    network_secret: Option<&[u8]>,
) -> EncryptedPayload {
    let key_bytes = derive_stream_key(stream_id, network_secret);
    let cipher = XChaCha20Poly1305::new_from_slice(&key_bytes).expect("key size");
    let nonce_bytes = derive_stream_nonce(stream_id, chunk_index);
    let nonce = XNonce::from_slice(&nonce_bytes);
    let ciphertext = cipher
        .encrypt(nonce, plaintext)
        .expect("encryption failure");

    EncryptedPayload {
        ciphertext,
        nonce: nonce_bytes,
        alg: ENCRYPTION_ALG,
    }
}

pub fn decrypt_stream_data(
    stream_id: &str,
    chunk_index: u64,
    ciphertext: &[u8],
    network_secret: Option<&[u8]>,
) -> Option<Vec<u8>> {
    let key_bytes = derive_stream_key(stream_id, network_secret);
    let cipher = XChaCha20Poly1305::new_from_slice(&key_bytes).expect("key size");
    let nonce_bytes = derive_stream_nonce(stream_id, chunk_index);
    let nonce = XNonce::from_slice(&nonce_bytes);
    cipher.decrypt(nonce, ciphertext).ok()
}

#[derive(Debug, Clone)]
pub struct ManifestKeypair {
    pub signing_key: SigningKey,
    pub verifying_key: VerifyingKey,
}

#[derive(Debug, Clone)]
pub struct EthereumManifestKeypair {
    pub signing_key: SecpSigningKey,
    pub verifying_key: SecpVerifyingKey,
}

fn decode_env_hex_or_file(value: &str) -> Result<Vec<u8>, String> {
    let trimmed = value.trim();
    if std::path::Path::new(trimmed).exists() {
        let text = fs::read_to_string(trimmed).map_err(|err| err.to_string())?;
        hex::decode(text.trim().trim_start_matches("0x")).map_err(|err| err.to_string())
    } else {
        hex::decode(trimmed.trim_start_matches("0x")).map_err(|err| err.to_string())
    }
}

pub fn load_manifest_keypair_from_env() -> Result<Option<ManifestKeypair>, String> {
    let value = match env::var("EVERY_CHANNEL_MANIFEST_SIGNING_KEY") {
        Ok(value) => value,
        Err(env::VarError::NotPresent) => return Ok(None),
        Err(err) => return Err(err.to_string()),
    };
    let key_bytes = decode_env_hex_or_file(&value)?;
    let bytes = if key_bytes.len() == 32 {
        key_bytes
    } else if key_bytes.len() == 64 {
        key_bytes[..32].to_vec()
    } else {
        return Err("manifest signing key must be 32 or 64 hex bytes".to_string());
    };
    let mut secret = [0u8; 32];
    secret.copy_from_slice(&bytes[..32]);
    let signing_key = SigningKey::from_bytes(&secret);
    let verifying_key = signing_key.verifying_key();
    Ok(Some(ManifestKeypair {
        signing_key,
        verifying_key,
    }))
}

pub fn load_ethereum_manifest_keypair_from_env() -> Result<Option<EthereumManifestKeypair>, String>
{
    let value = match env::var(ETH_MANIFEST_SIGNING_KEY_ENV) {
        Ok(value) => value,
        Err(env::VarError::NotPresent) => return Ok(None),
        Err(err) => return Err(err.to_string()),
    };
    let key_bytes = decode_env_hex_or_file(&value)?;
    if key_bytes.len() != 32 {
        return Err("ethereum manifest signing key must be exactly 32 hex bytes".to_string());
    }
    let signing_key =
        SecpSigningKey::from_bytes((&key_bytes[..]).into()).map_err(|err| err.to_string())?;
    let verifying_key = *signing_key.verifying_key();
    Ok(Some(EthereumManifestKeypair {
        signing_key,
        verifying_key,
    }))
}

pub fn signer_id_from_key(key: &VerifyingKey) -> String {
    format!("ed25519:{}", hex::encode(key.to_bytes()))
}

pub fn ethereum_signer_id_from_key(key: &SecpVerifyingKey) -> String {
    let encoded = key.to_encoded_point(false);
    let digest = Keccak256::digest(&encoded.as_bytes()[1..]);
    format!("eth:0x{}", hex::encode(&digest[12..]))
}

pub fn sign_manifest_id(manifest_id: &str, keypair: &ManifestKeypair) -> ManifestSignature {
    let signature: Signature = keypair.signing_key.sign(manifest_id.as_bytes());
    ManifestSignature {
        signer_id: signer_id_from_key(&keypair.verifying_key),
        alg: MANIFEST_SIG_ALG.to_string(),
        signature: hex::encode(signature.to_bytes()),
    }
}

pub fn sign_manifest_body_eip712(
    body: &ManifestBody,
    keypair: &EthereumManifestKeypair,
) -> Result<ManifestSignature, String> {
    let digest = manifest_body_eip712_signing_hash(body).map_err(|err| err.to_string())?;
    let (signature, recovery_id) = keypair
        .signing_key
        .sign_prehash_recoverable(digest.as_slice())
        .map_err(|err| err.to_string())?;
    let mut bytes = Vec::with_capacity(65);
    bytes.extend_from_slice(&signature.to_bytes());
    bytes.push(recovery_id.to_byte());
    Ok(ManifestSignature {
        signer_id: ethereum_signer_id_from_key(&keypair.verifying_key),
        alg: ETH_MANIFEST_SIG_ALG.to_string(),
        signature: hex::encode(bytes),
    })
}

pub fn verify_manifest_signature(manifest_id: &str, sig: &ManifestSignature) -> bool {
    if sig.alg != MANIFEST_SIG_ALG {
        return false;
    }
    let signer_id = sig
        .signer_id
        .strip_prefix("ed25519:")
        .unwrap_or(&sig.signer_id);
    let Ok(pk_bytes) = hex::decode(signer_id) else {
        return false;
    };
    if pk_bytes.len() != 32 {
        return false;
    }
    let mut pk = [0u8; 32];
    pk.copy_from_slice(&pk_bytes);
    let Ok(verifying_key) = VerifyingKey::from_bytes(&pk) else {
        return false;
    };
    let Ok(sig_bytes) = hex::decode(&sig.signature) else {
        return false;
    };
    let Ok(signature) = Signature::from_slice(&sig_bytes) else {
        return false;
    };
    verifying_key
        .verify(manifest_id.as_bytes(), &signature)
        .is_ok()
}

fn normalize_eth_signer_id(value: &str) -> Option<String> {
    let trimmed = value
        .strip_prefix("eth:")
        .or_else(|| value.strip_prefix("ETH:"))
        .unwrap_or(value)
        .trim();
    let address = trimmed.trim_start_matches("0x");
    if address.len() != 40 || !address.chars().all(|c| c.is_ascii_hexdigit()) {
        return None;
    }
    Some(format!("eth:0x{}", address.to_ascii_lowercase()))
}

pub fn verify_manifest_body_eip712_signature(body: &ManifestBody, sig: &ManifestSignature) -> bool {
    if sig.alg != ETH_MANIFEST_SIG_ALG {
        return false;
    }
    let Some(expected_signer_id) = normalize_eth_signer_id(&sig.signer_id) else {
        return false;
    };
    let Ok(sig_bytes) = hex::decode(sig.signature.trim().trim_start_matches("0x")) else {
        return false;
    };
    if sig_bytes.len() != 65 {
        return false;
    }
    let Ok(signature) = SecpSignature::from_slice(&sig_bytes[..64]) else {
        return false;
    };
    let Ok(recovery_id) = SecpRecoveryId::try_from(sig_bytes[64]) else {
        return false;
    };
    let Ok(digest) = manifest_body_eip712_signing_hash(body) else {
        return false;
    };
    let Ok(verifying_key) =
        SecpVerifyingKey::recover_from_prehash(digest.as_slice(), &signature, recovery_id)
    else {
        return false;
    };
    ethereum_signer_id_from_key(&verifying_key) == expected_signer_id
}

pub fn verify_manifest_signature_with_body(
    manifest_id: &str,
    body: &ManifestBody,
    sig: &ManifestSignature,
) -> bool {
    verify_manifest_signature(manifest_id, sig) || verify_manifest_body_eip712_signature(body, sig)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn stream_key_is_deterministic_and_secret_sensitive() {
        let k1 = derive_stream_key("s1", None);
        let k2 = derive_stream_key("s1", None);
        assert_eq!(k1, k2);
        let k3 = derive_stream_key("s2", None);
        assert_ne!(k1, k3);

        let secret = [7u8; 32];
        let ks1 = derive_stream_key("s1", Some(&secret));
        assert_ne!(k1, ks1);
        let ks2 = derive_stream_key("s1", Some(&secret));
        assert_eq!(ks1, ks2);
    }

    #[test]
    fn nonce_changes_per_chunk_index() {
        let n1 = derive_stream_nonce("s", 1);
        let n2 = derive_stream_nonce("s", 2);
        assert_ne!(n1, n2);
    }

    #[test]
    fn encrypt_decrypt_roundtrip() {
        let plaintext = b"hello world";
        let enc = encrypt_stream_data("s", 42, plaintext, None);
        assert_ne!(enc.ciphertext, plaintext);
        let out = decrypt_stream_data("s", 42, &enc.ciphertext, None).unwrap();
        assert_eq!(out, plaintext);
    }

    #[test]
    fn decrypt_fails_with_wrong_index() {
        let plaintext = b"hello world";
        let enc = encrypt_stream_data("s", 42, plaintext, None);
        assert!(decrypt_stream_data("s", 43, &enc.ciphertext, None).is_none());
    }

    #[test]
    fn manifest_sign_verify_roundtrip() {
        let secret = [1u8; 32];
        let signing_key = SigningKey::from_bytes(&secret);
        let verifying_key = signing_key.verifying_key();
        let keypair = ManifestKeypair {
            signing_key,
            verifying_key,
        };
        let sig = sign_manifest_id("m", &keypair);
        assert!(verify_manifest_signature("m", &sig));
        assert!(!verify_manifest_signature("evil", &sig));
    }

    #[test]
    fn ethereum_manifest_sign_verify_roundtrip() {
        let body = ManifestBody {
            stream_id: ec_core::StreamId("stream".to_string()),
            epoch_id: "epoch-1".to_string(),
            chunk_duration_ms: 2000,
            total_chunks: 1,
            chunk_start_index: 0,
            encoder_profile_id: "p".to_string(),
            merkle_root: "11".repeat(32),
            created_unix_ms: 1,
            metadata: Vec::new(),
            chunk_hashes: vec!["22".repeat(32)],
            variants: None,
        };
        let secret = [7u8; 32];
        let signing_key = SecpSigningKey::from_bytes((&secret).into()).unwrap();
        let verifying_key = *signing_key.verifying_key();
        let keypair = EthereumManifestKeypair {
            signing_key,
            verifying_key,
        };
        let sig = sign_manifest_body_eip712(&body, &keypair).unwrap();
        assert!(verify_manifest_body_eip712_signature(&body, &sig));
        let mut tampered = body.clone();
        tampered.created_unix_ms = 2;
        assert!(!verify_manifest_body_eip712_signature(&tampered, &sig));
    }

    #[test]
    fn load_keypair_from_env_hex() {
        let prev = env::var("EVERY_CHANNEL_MANIFEST_SIGNING_KEY").ok();
        env::set_var("EVERY_CHANNEL_MANIFEST_SIGNING_KEY", "00".repeat(32));
        let loaded = load_manifest_keypair_from_env().unwrap().unwrap();
        let id = signer_id_from_key(&loaded.verifying_key);
        assert!(id.starts_with("ed25519:"));
        match prev {
            Some(value) => env::set_var("EVERY_CHANNEL_MANIFEST_SIGNING_KEY", value),
            None => env::remove_var("EVERY_CHANNEL_MANIFEST_SIGNING_KEY"),
        }
    }

    #[test]
    fn load_ethereum_keypair_from_env_hex() {
        let prev = env::var(ETH_MANIFEST_SIGNING_KEY_ENV).ok();
        env::set_var(ETH_MANIFEST_SIGNING_KEY_ENV, "01".repeat(32));
        let loaded = load_ethereum_manifest_keypair_from_env().unwrap().unwrap();
        let id = ethereum_signer_id_from_key(&loaded.verifying_key);
        assert!(id.starts_with("eth:0x"));
        match prev {
            Some(value) => env::set_var(ETH_MANIFEST_SIGNING_KEY_ENV, value),
            None => env::remove_var(ETH_MANIFEST_SIGNING_KEY_ENV),
        }
    }
}
