use serde::{Deserialize, Serialize};

use crate::permissions::SandboxGrant;

/// Store abstraction for persisted user-approved sandbox grants.
///
/// Hosts own the actual persistence policy. This trait keeps the shared
/// preflight logic independent of any specific filesystem, database, or
/// platform key-value API.
pub trait SandboxGrantStore {
    fn load_grant(
        &self,
        product_id: &str,
        bundle_hash_or_cid: &str,
    ) -> Result<Option<SandboxGrant>, SandboxGrantStoreError>;

    fn save_grant(&mut self, grant: SandboxGrant) -> Result<(), SandboxGrantStoreError>;
}

/// Minimal serializable grant store that hosts can persist using their own
/// storage layer.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct InMemorySandboxGrantStore {
    grants: Vec<SandboxGrant>,
}

impl InMemorySandboxGrantStore {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn from_grants(grants: Vec<SandboxGrant>) -> Result<Self, SandboxGrantStoreError> {
        let mut store = Self::default();
        for grant in grants {
            store.save_grant(grant)?;
        }
        Ok(store)
    }

    pub fn grants(&self) -> &[SandboxGrant] {
        &self.grants
    }
}

impl SandboxGrantStore for InMemorySandboxGrantStore {
    fn load_grant(
        &self,
        product_id: &str,
        bundle_hash_or_cid: &str,
    ) -> Result<Option<SandboxGrant>, SandboxGrantStoreError> {
        validate_key(product_id, bundle_hash_or_cid)?;

        Ok(self
            .grants
            .iter()
            .find(|grant| {
                grant.product_id == product_id && grant.bundle_hash_or_cid == bundle_hash_or_cid
            })
            .cloned())
    }

    fn save_grant(&mut self, grant: SandboxGrant) -> Result<(), SandboxGrantStoreError> {
        validate_key(&grant.product_id, &grant.bundle_hash_or_cid)?;

        if let Some(existing) = self.grants.iter_mut().find(|existing| {
            existing.product_id == grant.product_id
                && existing.bundle_hash_or_cid == grant.bundle_hash_or_cid
        }) {
            *existing = grant;
        } else {
            self.grants.push(grant);
        }

        Ok(())
    }
}

#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum SandboxGrantStoreError {
    #[error("sandbox grant store rejected empty product id")]
    EmptyProductId,

    #[error("sandbox grant store rejected empty bundle hash or cid")]
    EmptyBundleHashOrCid,
}

fn validate_key(product_id: &str, bundle_hash_or_cid: &str) -> Result<(), SandboxGrantStoreError> {
    if product_id.trim().is_empty() {
        return Err(SandboxGrantStoreError::EmptyProductId);
    }
    if bundle_hash_or_cid.trim().is_empty() {
        return Err(SandboxGrantStoreError::EmptyBundleHashOrCid);
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{permissions::GrantedSandboxRequest, SandboxGrant, SandboxRequestKind};

    fn test_grant(product_id: &str, bundle_hash_or_cid: &str, origin: &str) -> SandboxGrant {
        SandboxGrant {
            product_id: product_id.into(),
            bundle_hash_or_cid: bundle_hash_or_cid.into(),
            requests: vec![GrantedSandboxRequest {
                kind: SandboxRequestKind::Network,
                origins: vec![origin.into()],
            }],
        }
    }

    #[test]
    fn load_returns_matching_grant_for_product_and_artifact() {
        let store = InMemorySandboxGrantStore::from_grants(vec![test_grant(
            "example.dot",
            "bafytest",
            "https://api.example.com",
        )])
        .unwrap();

        let loaded = store
            .load_grant("example.dot", "bafytest")
            .unwrap()
            .unwrap();
        assert_eq!(loaded.bundle_hash_or_cid, "bafytest");
    }

    #[test]
    fn save_replaces_existing_grant_for_same_key() {
        let mut store = InMemorySandboxGrantStore::from_grants(vec![test_grant(
            "example.dot",
            "bafytest",
            "https://old.example.com",
        )])
        .unwrap();

        store
            .save_grant(test_grant(
                "example.dot",
                "bafytest",
                "https://new.example.com",
            ))
            .unwrap();

        assert_eq!(store.grants().len(), 1);
        assert_eq!(
            store.grants()[0].requests[0].origins,
            vec!["https://new.example.com".to_string()]
        );
    }

    #[test]
    fn rejects_empty_keys() {
        let mut store = InMemorySandboxGrantStore::default();

        let err = store
            .save_grant(test_grant("", "bafytest", "https://api.example.com"))
            .unwrap_err();
        assert_eq!(err, SandboxGrantStoreError::EmptyProductId);

        let err = store.load_grant("example.dot", "").unwrap_err();
        assert_eq!(err, SandboxGrantStoreError::EmptyBundleHashOrCid);
    }
}
