//! Filesystem-backed [`McpToolPermissionStore`] for project MCP tool permissions.

use std::sync::Arc;

use async_trait::async_trait;

use domain::ports::{FileSystem, FsError, McpToolPermissionStore, RemotePath, StoreError};
use domain::{Project, ProjectMcpToolPermissions};

use crate::orchestrator::mcp::tools;

const MCP_TOOL_PERMISSIONS_FILE: &str = "mcp-tool-permissions.json";

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

impl FsMcpToolPermissionStore {
    /// 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/{MCP_TOOL_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()))
    }

    fn validate(permissions: &ProjectMcpToolPermissions) -> Result<(), StoreError> {
        let known_tools = tools::classified_tool_names();
        permissions
            .validate(&known_tools)
            .map_err(|e| StoreError::Serialization(e.to_string()))
    }
}

#[async_trait]
impl McpToolPermissionStore for FsMcpToolPermissionStore {
    async fn load_mcp_tool_permissions(
        &self,
        project: &Project,
    ) -> Result<ProjectMcpToolPermissions, StoreError> {
        match self.fs.read(&Self::path(project)).await {
            Ok(bytes) => {
                let permissions = serde_json::from_slice::<ProjectMcpToolPermissions>(&bytes)
                    .map_err(|e| StoreError::Serialization(e.to_string()))?;
                Self::validate(&permissions)?;
                Ok(permissions)
            }
            Err(FsError::NotFound(_)) => Ok(ProjectMcpToolPermissions::default()),
            Err(e) => Err(StoreError::Io(e.to_string())),
        }
    }

    async fn save_mcp_tool_permissions(
        &self,
        project: &Project,
        permissions: &ProjectMcpToolPermissions,
    ) -> Result<(), StoreError> {
        Self::validate(permissions)?;
        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()))
    }
}
