//! Org vault types, crypto, and schema for multi-user self-hosted deployments.

use rand::{rngs::OsRng, RngCore};
use serde::{Deserialize, Serialize};
use zeroize::Zeroizing;

use crate::error::{RelicarioError, Result};
use crate::ids::ItemId;
use crate::item_types::ItemType;

// ── IDs ──────────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct OrgId(pub String);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MemberId(pub String);

impl OrgId {
    pub fn new() -> Self {
        let mut bytes = [0u8; 8];
        OsRng.fill_bytes(&mut bytes);
        Self(hex::encode(bytes))
    }
    pub fn as_str(&self) -> &str { &self.0 }
}

impl Default for OrgId {
    fn default() -> Self { Self::new() }
}

impl MemberId {
    pub fn new() -> Self {
        let mut bytes = [0u8; 8];
        OsRng.fill_bytes(&mut bytes);
        Self(hex::encode(bytes))
    }
    pub fn as_str(&self) -> &str { &self.0 }
    pub fn is_valid(&self) -> bool {
        self.0.len() == 16 && self.0.chars().all(|c| c.is_ascii_hexdigit())
    }
}

impl Default for MemberId {
    fn default() -> Self { Self::new() }
}

// ── Roles ────────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OrgRole {
    Owner,
    Admin,
    Member,
}

impl OrgRole {
    pub fn can_manage_members(&self) -> bool {
        matches!(self, OrgRole::Owner | OrgRole::Admin)
    }
    pub fn can_manage_owners(&self) -> bool {
        matches!(self, OrgRole::Owner)
    }
}

// ── Members ──────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgMember {
    pub member_id: MemberId,
    pub display_name: String,
    pub role: OrgRole,
    /// SSH public key string (openssh format: "ssh-ed25519 AAAA...")
    pub ed25519_pubkey: String,
    /// Collection slugs this member can access.
    #[serde(default)]
    pub collections: Vec<String>,
    pub added_at: i64,
    pub added_by: MemberId,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgMembers {
    pub schema_version: u32,
    pub members: Vec<OrgMember>,
}

impl OrgMembers {
    pub fn new() -> Self {
        Self { schema_version: 1, members: Vec::new() }
    }

    pub fn find_by_id(&self, id: &MemberId) -> Option<&OrgMember> {
        self.members.iter().find(|m| &m.member_id == id)
    }

    pub fn find_by_id_mut(&mut self, id: &MemberId) -> Option<&mut OrgMember> {
        self.members.iter_mut().find(|m| &m.member_id == id)
    }

    pub fn validate(&self) -> Result<()> {
        for m in &self.members {
            if !m.member_id.is_valid() {
                return Err(RelicarioError::Format(
                    format!("invalid member_id: {}", m.member_id.0)
                ));
            }
        }
        Ok(())
    }
}

impl Default for OrgMembers {
    fn default() -> Self { Self::new() }
}

// ── Collections ───────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectionDef {
    pub slug: String,
    pub display_name: String,
    pub created_by: MemberId,
    pub created_at: i64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgCollections {
    pub schema_version: u32,
    pub collections: Vec<CollectionDef>,
}

impl OrgCollections {
    pub fn new() -> Self {
        Self { schema_version: 1, collections: Vec::new() }
    }

    pub fn contains_slug(&self, slug: &str) -> bool {
        self.collections.iter().any(|c| c.slug == slug)
    }

    pub fn validate(&self) -> Result<()> {
        for c in &self.collections {
            if c.slug.is_empty() || c.slug.contains('/') || c.slug.contains('.') {
                return Err(RelicarioError::Format(
                    format!("invalid collection slug: {:?}", c.slug)
                ));
            }
        }
        Ok(())
    }
}

impl Default for OrgCollections {
    fn default() -> Self { Self::new() }
}

// ── Org meta ─────────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgMeta {
    pub schema_version: u32,
    pub org_id: OrgId,
    pub display_name: String,
    pub created_at: i64,
}

impl OrgMeta {
    pub fn new(display_name: String) -> Self {
        Self {
            schema_version: 1,
            org_id: OrgId::new(),
            display_name,
            created_at: crate::time::now_unix(),
        }
    }
}

// ── Org manifest ─────────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgManifestEntry {
    pub id: ItemId,
    pub r#type: ItemType,
    pub title: String,
    #[serde(default)]
    pub tags: Vec<String>,
    pub modified: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trashed_at: Option<i64>,
    /// Collection this item belongs to.
    pub collection: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgManifest {
    pub schema_version: u32,
    pub entries: Vec<OrgManifestEntry>,
}

impl OrgManifest {
    pub fn new() -> Self {
        Self { schema_version: 1, entries: Vec::new() }
    }

    /// Return only entries whose collection is in `member.collections`.
    pub fn filter_for_member(&self, member: &OrgMember) -> Self {
        let granted: std::collections::HashSet<&str> =
            member.collections.iter().map(|s| s.as_str()).collect();
        Self {
            schema_version: self.schema_version,
            entries: self.entries.iter()
                .filter(|e| granted.contains(e.collection.as_str()))
                .cloned()
                .collect(),
        }
    }
}

impl Default for OrgManifest {
    fn default() -> Self { Self::new() }
}

// ── Key wrap / unwrap (ECIES: X25519 + XChaCha20-Poly1305) ───────────────────

/// Generate a random 256-bit org master key.
pub fn generate_org_key() -> Zeroizing<[u8; 32]> {
    let mut key = Zeroizing::new([0u8; 32]);
    OsRng.fill_bytes(key.as_mut());
    key
}

/// Derive an X25519 static secret from an ed25519 seed (standard RFC 7748 path).
fn ed25519_seed_to_x25519_secret(seed: &[u8; 32]) -> x25519_dalek::StaticSecret {
    use sha2::{Digest, Sha512};
    let h = Sha512::digest(seed.as_ref());
    let mut scalar = [0u8; 32];
    scalar.copy_from_slice(&h[..32]);
    // RFC 7748 clamping
    scalar[0] &= 248;
    scalar[31] &= 127;
    scalar[31] |= 64;
    x25519_dalek::StaticSecret::from(scalar)
}

/// Parse an OpenSSH ed25519 public key string and return its X25519 form.
fn openssh_ed25519_to_x25519_pk(openssh: &str) -> Result<x25519_dalek::PublicKey> {
    use ssh_key::PublicKey;
    let pk = PublicKey::from_openssh(openssh.trim())
        .map_err(|e| RelicarioError::Format(format!("bad SSH pubkey: {e}")))?;
    let ed_bytes = pk.key_data().ed25519()
        .ok_or_else(|| RelicarioError::Format("expected ed25519 key".into()))?
        .0;
    let verifying = ed25519_dalek::VerifyingKey::from_bytes(&ed_bytes)
        .map_err(|e| RelicarioError::Format(format!("bad ed25519 pubkey: {e}")))?;
    Ok(x25519_dalek::PublicKey::from(verifying.to_montgomery().to_bytes()))
}

/// Wrap `org_key` for a recipient identified by their OpenSSH ed25519 public key.
///
/// Output layout: `ephemeral_x25519_pk(32) || version(1) || nonce(24) || ciphertext+tag`
pub fn wrap_org_key(org_key: &Zeroizing<[u8; 32]>, recipient_openssh_pubkey: &str) -> Result<Vec<u8>> {
    use sha2::{Digest, Sha256};
    use x25519_dalek::EphemeralSecret;

    let recipient_pk = openssh_ed25519_to_x25519_pk(recipient_openssh_pubkey)?;

    let ephemeral_sk = EphemeralSecret::random_from_rng(OsRng);
    let ephemeral_pk = x25519_dalek::PublicKey::from(&ephemeral_sk);

    let shared = ephemeral_sk.diffie_hellman(&recipient_pk);

    // Domain-separated KDF. All intermediates carrying the DH secret are held in
    // Zeroizing so they are wiped on drop (H6).
    let mut kdf_input: Zeroizing<Vec<u8>> = Zeroizing::new(Vec::with_capacity(32 + 32 + 32));
    kdf_input.extend_from_slice(shared.as_bytes());
    kdf_input.extend_from_slice(ephemeral_pk.as_bytes());
    kdf_input.extend_from_slice(recipient_pk.as_bytes());

    // Copy the digest straight into a Zeroizing array. The GenericArray returned
    // by Sha256::digest is not Zeroize (generic-array's impl is feature-gated and
    // not enabled here), so we move the bytes into an owned [u8; 32] whose own
    // Zeroize impl wipes them on drop.
    let mut wrap_key: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
    wrap_key.copy_from_slice(&Sha256::digest(kdf_input.as_slice()));

    let encrypted = crate::crypto::encrypt(&wrap_key, org_key.as_ref())?;

    let mut out = Vec::with_capacity(32 + encrypted.len());
    out.extend_from_slice(ephemeral_pk.as_bytes());
    out.extend_from_slice(&encrypted);
    Ok(out)
}

/// Unwrap a key blob produced by `wrap_org_key` using the recipient's ed25519 seed.
pub fn unwrap_org_key(wrapped: &[u8], ed25519_seed: &Zeroizing<[u8; 32]>) -> Result<Zeroizing<[u8; 32]>> {
    use sha2::{Digest, Sha256};

    // Minimum: 32 (ephemeral_pk) + 41 (version+nonce+tag for 32-byte plaintext)
    if wrapped.len() < 32 + 41 {
        return Err(RelicarioError::Format("wrapped key blob too short".into()));
    }

    let mut eph_bytes = [0u8; 32];
    eph_bytes.copy_from_slice(&wrapped[..32]);
    let ephemeral_pk = x25519_dalek::PublicKey::from(eph_bytes);
    let encrypted = &wrapped[32..];

    let recipient_sk = ed25519_seed_to_x25519_secret(ed25519_seed);
    let recipient_pk = x25519_dalek::PublicKey::from(&recipient_sk);

    let shared = recipient_sk.diffie_hellman(&ephemeral_pk);

    let mut kdf_input: Zeroizing<Vec<u8>> = Zeroizing::new(Vec::with_capacity(32 + 32 + 32));
    kdf_input.extend_from_slice(shared.as_bytes());
    kdf_input.extend_from_slice(ephemeral_pk.as_bytes());
    kdf_input.extend_from_slice(recipient_pk.as_bytes());

    let mut wrap_key: Zeroizing<[u8; 32]> = Zeroizing::new([0u8; 32]);
    wrap_key.copy_from_slice(&Sha256::digest(kdf_input.as_slice()));

    let plaintext = Zeroizing::new(crate::crypto::decrypt(&wrap_key, encrypted)?);
    if plaintext.len() != 32 {
        return Err(RelicarioError::Format(
            format!("unwrapped key has wrong length: {}", plaintext.len())
        ));
    }

    let mut key = Zeroizing::new([0u8; 32]);
    key.copy_from_slice(&plaintext);
    Ok(key)
}

// ── Tests ─────────────────────────────────────────────────────────────────────

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

    #[test]
    fn member_id_is_16_hex_chars() {
        let id = MemberId::new();
        assert_eq!(id.0.len(), 16);
        assert!(id.0.chars().all(|c| c.is_ascii_hexdigit()));
    }

    #[test]
    fn member_ids_are_unique() {
        let mut seen = std::collections::HashSet::new();
        for _ in 0..1_000 {
            assert!(seen.insert(MemberId::new().0));
        }
    }

    #[test]
    fn org_id_is_16_hex_chars() {
        let id = OrgId::new();
        assert_eq!(id.0.len(), 16);
        assert!(id.0.chars().all(|c| c.is_ascii_hexdigit()));
    }

    #[test]
    fn org_role_can_manage_members() {
        assert!(OrgRole::Owner.can_manage_members());
        assert!(OrgRole::Admin.can_manage_members());
        assert!(!OrgRole::Member.can_manage_members());
    }

    #[test]
    fn collection_slug_validation_rejects_slash() {
        let mut c = OrgCollections::new();
        c.collections.push(CollectionDef {
            slug: "bad/slug".into(),
            display_name: "Bad".into(),
            created_by: MemberId::new(),
            created_at: 0,
        });
        assert!(c.validate().is_err());
    }

    #[test]
    fn filter_for_member_restricts_collections() {
        let mut manifest = OrgManifest::new();
        manifest.entries.push(OrgManifestEntry {
            id: ItemId::new(),
            r#type: crate::item_types::ItemType::SecureNote,
            title: "A".into(),
            tags: vec![],
            modified: 0,
            trashed_at: None,
            collection: "prod".into(),
        });
        manifest.entries.push(OrgManifestEntry {
            id: ItemId::new(),
            r#type: crate::item_types::ItemType::SecureNote,
            title: "B".into(),
            tags: vec![],
            modified: 0,
            trashed_at: None,
            collection: "dev".into(),
        });

        let member = OrgMember {
            member_id: MemberId::new(),
            display_name: "Alice".into(),
            role: OrgRole::Member,
            ed25519_pubkey: String::new(),
            collections: vec!["prod".into()],
            added_at: 0,
            added_by: MemberId::new(),
        };

        let filtered = manifest.filter_for_member(&member);
        assert_eq!(filtered.entries.len(), 1);
        assert_eq!(filtered.entries[0].collection, "prod");
    }

    #[test]
    fn generate_org_key_is_32_bytes() {
        let key = generate_org_key();
        assert_eq!(key.len(), 32);
    }

    /// Pinned RFC 8032 known-answer vector for the ed25519→X25519 map. The seed
    /// and expected X25519 public key are from ed25519-dalek's own reference
    /// test (`tests/x25519.rs`, section 7.1 vector A). The expected value is a
    /// HARD-CODED LITERAL — NOT recomputed by the production code path — so a
    /// correlated cross-crate-version regression in the birational map (where
    /// both our derivation and a naive re-derivation would drift together) is
    /// still caught. If this test ever fails after a dep bump, the wrap/unwrap
    /// keyspace changed and every existing `keys/<id>.enc` blob is invalidated.
    #[test]
    fn ed25519_to_x25519_pinned_rfc8032_vector() {
        let seed: [u8; 32] =
            hex::decode("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")
                .unwrap()
                .try_into()
                .unwrap();
        // Derive the X25519 *public* key the same way wrap/unwrap derives the
        // recipient's static secret from a seed.
        let secret = ed25519_seed_to_x25519_secret(&seed);
        let public = x25519_dalek::PublicKey::from(&secret);
        assert_eq!(
            hex::encode(public.as_bytes()),
            "d85e07ec22b0ad881537c2f44d662d1a143cf830c57aca4305d85c7a90f6b62e",
        );
    }

    #[test]
    fn wrap_unwrap_round_trip() {
        // Generate an ed25519 keypair to act as the member's device key
        use ed25519_dalek::SigningKey;
        let mut seed = [0u8; 32];
        OsRng.fill_bytes(&mut seed);
        let signing_key = SigningKey::from_bytes(&seed);
        let pubkey_openssh = ssh_key::PrivateKey::from(
            ssh_key::private::Ed25519Keypair::from(&signing_key),
        )
        .public_key()
        .to_openssh()
        .expect("openssh");

        let org_key = generate_org_key();
        let wrapped = wrap_org_key(&org_key, &pubkey_openssh).expect("wrap");
        let seed_zeroizing = Zeroizing::new(seed);
        let unwrapped = unwrap_org_key(&wrapped, &seed_zeroizing).expect("unwrap");

        assert_eq!(*org_key, *unwrapped);
    }

    #[test]
    fn unwrap_with_wrong_seed_fails() {
        use ed25519_dalek::SigningKey;
        let mut seed = [0u8; 32];
        OsRng.fill_bytes(&mut seed);
        let signing_key = SigningKey::from_bytes(&seed);
        let pubkey_openssh = ssh_key::PrivateKey::from(
            ssh_key::private::Ed25519Keypair::from(&signing_key),
        )
        .public_key()
        .to_openssh()
        .expect("openssh");

        let org_key = generate_org_key();
        let wrapped = wrap_org_key(&org_key, &pubkey_openssh).expect("wrap");

        let wrong_seed = Zeroizing::new([0xFFu8; 32]);
        let result = unwrap_org_key(&wrapped, &wrong_seed);
        assert!(result.is_err());
    }
}
