//! Filesystem-backed [`PermissionStore`] for project agent permissions.

use std::sync::Arc;

use async_trait::async_trait;

use domain::ports::{FileSystem, FsError, PermissionStore, RemotePath, StoreError};
use domain::{Project, ProjectPermissions};

const PERMISSIONS_FILE: &str = "permissions.json";

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

impl FsPermissionStore {
    /// 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/{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 PermissionStore for FsPermissionStore {
    async fn load_permissions(&self, project: &Project) -> Result<ProjectPermissions, 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(ProjectPermissions::default()),
            Err(e) => Err(StoreError::Io(e.to_string())),
        }
    }

    async fn save_permissions(
        &self,
        project: &Project,
        permissions: &ProjectPermissions,
    ) -> 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()))
    }
}
