//! Organization storage.

use std::sync::Arc;

use iam_types::{Error, IamError, Organization, Result};

use crate::backend::{Backend, CasResult, JsonStore, StorageBackend};

mod keys {
    pub const ORGS: &str = "iam/orgs/";
}

pub struct OrgStore {
    backend: Arc<Backend>,
}

impl JsonStore for OrgStore {
    fn backend(&self) -> &Backend {
        &self.backend
    }
}

impl OrgStore {
    pub fn new(backend: Arc<Backend>) -> Self {
        Self { backend }
    }

    pub async fn create(&self, org: &Organization) -> Result<u64> {
        let key = self.primary_key(&org.id);
        let bytes = serde_json::to_vec(org).map_err(|e| Error::Serialization(e.to_string()))?;
        match self.backend.cas(key.as_bytes(), 0, &bytes).await? {
            CasResult::Success(version) => Ok(version),
            CasResult::Conflict { .. } => {
                Err(Error::Iam(IamError::OrganizationAlreadyExists(org.id.clone())))
            }
            CasResult::NotFound => Err(Error::Internal("Unexpected CAS result".into())),
        }
    }

    pub async fn create_if_missing(&self, org: &Organization) -> Result<bool> {
        let key = self.primary_key(&org.id);
        let bytes = serde_json::to_vec(org).map_err(|e| Error::Serialization(e.to_string()))?;
        match self.backend.cas(key.as_bytes(), 0, &bytes).await? {
            CasResult::Success(_) => Ok(true),
            CasResult::Conflict { .. } => Ok(false),
            CasResult::NotFound => Err(Error::Internal("Unexpected CAS result".into())),
        }
    }

    pub async fn get(&self, id: &str) -> Result<Option<Organization>> {
        Ok(self.get_json::<Organization>(self.primary_key(id).as_bytes()).await?.map(|v| v.0))
    }

    pub async fn get_with_version(&self, id: &str) -> Result<Option<(Organization, u64)>> {
        self.get_json::<Organization>(self.primary_key(id).as_bytes()).await
    }

    pub async fn update(&self, org: &Organization, expected_version: u64) -> Result<u64> {
        let key = self.primary_key(&org.id);
        let bytes = serde_json::to_vec(org).map_err(|e| Error::Serialization(e.to_string()))?;
        match self.backend.cas(key.as_bytes(), expected_version, &bytes).await? {
            CasResult::Success(version) => Ok(version),
            CasResult::Conflict { expected, actual } => {
                Err(Error::Storage(iam_types::StorageError::CasConflict { expected, actual }))
            }
            CasResult::NotFound => Err(Error::Iam(IamError::OrganizationNotFound(org.id.clone()))),
        }
    }

    pub async fn delete(&self, id: &str) -> Result<bool> {
        self.backend.delete(self.primary_key(id).as_bytes()).await
    }

    pub async fn list(&self) -> Result<Vec<Organization>> {
        let pairs = self.backend.scan_prefix(keys::ORGS.as_bytes(), 10_000).await?;
        let mut orgs = Vec::new();
        for pair in pairs {
            let org: Organization = serde_json::from_slice(&pair.value)
                .map_err(|e| Error::Serialization(e.to_string()))?;
            orgs.push(org);
        }
        Ok(orgs)
    }

    pub async fn exists(&self, id: &str) -> Result<bool> {
        Ok(self.backend.get(self.primary_key(id).as_bytes()).await?.is_some())
    }

    fn primary_key(&self, id: &str) -> String {
        format!("{}{id}", keys::ORGS)
    }
}

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

    #[tokio::test]
    async fn crud_roundtrip() {
        let backend = Arc::new(Backend::memory());
        let store = OrgStore::new(backend);
        let org = Organization::new("org-1", "Org 1");
        store.create(&org).await.unwrap();
        let fetched = store.get("org-1").await.unwrap().unwrap();
        assert_eq!(fetched.name, "Org 1");
        assert!(store.delete("org-1").await.unwrap());
        assert!(store.get("org-1").await.unwrap().is_none());
    }
}
