//! Filesystem-backed [`SystemPermissionStore`] for project system permissions.

use std::sync::Arc;

use async_trait::async_trait;

use domain::ports::{FileSystem, FsError, RemotePath, StoreError, SystemPermissionStore};
use domain::{Project, ProjectSystemPermissions};

const SYSTEM_PERMISSIONS_FILE: &str = "system-permissions.json";

/// JSON-file implementation for `<project>/.ideai/system-permissions.json`.
#[derive(Clone)]
pub struct FsSystemPermissionStore {
    fs: Arc<dyn FileSystem>,
}

impl FsSystemPermissionStore {
    /// Builds the store from an injected filesystem port.
    #[must_use]
    pub fn new(fs: Arc<dyn FileSystem>) -> Self {
        Self { fs }
    }

    fn path(project: &Project) -> RemotePath {
        let root = project.root.as_str().trim_end_matches(['/', '\\']);
        RemotePath::new(format!("{root}/.ideai/{SYSTEM_PERMISSIONS_FILE}"))
    }

    async fn ensure_ideai(&self, project: &Project) -> Result<(), StoreError> {
        let root = project.root.as_str().trim_end_matches(['/', '\\']);
        self.fs
            .create_dir_all(&RemotePath::new(format!("{root}/.ideai")))
            .await
            .map_err(|e| StoreError::Io(e.to_string()))
    }
}

#[async_trait]
impl SystemPermissionStore for FsSystemPermissionStore {
    async fn load_system_permissions(
        &self,
        project: &Project,
    ) -> Result<ProjectSystemPermissions, StoreError> {
        match self.fs.read(&Self::path(project)).await {
            Ok(bytes) => {
                serde_json::from_slice(&bytes).map_err(|e| StoreError::Serialization(e.to_string()))
            }
            Err(FsError::NotFound(_)) => Ok(ProjectSystemPermissions::default()),
            Err(e) => Err(StoreError::Io(e.to_string())),
        }
    }

    async fn save_system_permissions(
        &self,
        project: &Project,
        permissions: &ProjectSystemPermissions,
    ) -> Result<(), StoreError> {
        self.ensure_ideai(project).await?;
        let mut bytes = serde_json::to_vec_pretty(permissions)
            .map_err(|e| StoreError::Serialization(e.to_string()))?;
        bytes.push(b'\n');
        self.fs
            .write(&Self::path(project), &bytes)
            .await
            .map_err(|e| StoreError::Io(e.to_string()))
    }
}
