//! `VarsService` — the runtime read path for group-inherited config.
//!
//! Resolves the calling app's config (own rows + inherited group rows,
//! env-filtered, proximity-first; see `config_resolver`) and exposes it to
//! scripts as `vars::get(key)` / `vars::all()`. Read-only from scripts;
//! writes go through the admin API. Like every SDK service, it derives the
//! app from `cx.app_id` — never a script argument — so cross-app isolation
//! holds.

use std::collections::BTreeMap;
use std::sync::Arc;

use async_trait::async_trait;
use picloud_shared::{SdkCallCx, VarsError, VarsService};
use serde_json::Value;
use sqlx::PgPool;

use crate::authz::{self, AuthzRepo, Capability};
use crate::config_resolver::{fetch_var_candidates, resolve};

pub struct VarsServiceImpl {
    pool: PgPool,
    authz: Arc<dyn AuthzRepo>,
}

impl VarsServiceImpl {
    #[must_use]
    pub fn new(pool: PgPool, authz: Arc<dyn AuthzRepo>) -> Self {
        Self { pool, authz }
    }

    /// Authed principals need `AppVarsRead`; anonymous public-HTTP scripts
    /// (`principal: None`) read freely under script-as-gate semantics.
    async fn check_read(&self, cx: &SdkCallCx) -> Result<(), VarsError> {
        if let Some(ref principal) = cx.principal {
            authz::require(&*self.authz, principal, Capability::AppVarsRead(cx.app_id))
                .await
                .map_err(|_| VarsError::Forbidden)?;
        }
        Ok(())
    }

    async fn resolved(&self, cx: &SdkCallCx) -> Result<BTreeMap<String, Value>, VarsError> {
        let candidates = fetch_var_candidates(&self.pool, cx.app_id)
            .await
            .map_err(|e| VarsError::Backend(e.to_string()))?;
        let (values, _provenance) = resolve(candidates);
        Ok(values)
    }
}

#[async_trait]
impl VarsService for VarsServiceImpl {
    async fn get(&self, cx: &SdkCallCx, key: &str) -> Result<Option<Value>, VarsError> {
        self.check_read(cx).await?;
        Ok(self.resolved(cx).await?.remove(key))
    }

    async fn all(&self, cx: &SdkCallCx) -> Result<BTreeMap<String, Value>, VarsError> {
        self.check_read(cx).await?;
        self.resolved(cx).await
    }
}
