//! `WorkflowServiceImpl` — backs the Rhai `workflow::start(name, input)` SDK
//! call (v1.2 Workflows M5).
//!
//! Resolves a workflow **by name in the caller's app** (`cx.app_id` — never a
//! script-passed arg, the isolation boundary), seeds a durable run, and returns
//! its id. The dedicated orchestrator advances the run from there. Same-app
//! only, mirroring `InvokeServiceImpl`.
//!
//! Like `invoke()`, an authenticated caller is gated on `AppInvoke`; an
//! anonymous caller skips the check under the script-as-gate convention (the
//! public script IS the authorization boundary).

use std::sync::Arc;

use async_trait::async_trait;
use picloud_shared::{SdkCallCx, WorkflowError, WorkflowRunId, WorkflowService};
use sqlx::PgPool;

use crate::authz::{self, AuthzRepo, Capability};
use crate::workflow_repo::{get_workflow_by_name, start_run, NewWorkflowRun};

pub struct WorkflowServiceImpl {
    pool: PgPool,
    /// Optional. When set, `start` requires `AppInvoke` for authenticated
    /// callers; anonymous callers skip the check (script-as-gate).
    authz: Option<Arc<dyn AuthzRepo>>,
}

impl WorkflowServiceImpl {
    #[must_use]
    pub fn new(pool: PgPool) -> Self {
        Self { pool, authz: None }
    }

    /// Attach the authz repo so authenticated callers are gated on `AppInvoke`.
    #[must_use]
    pub fn with_authz(mut self, authz: Arc<dyn AuthzRepo>) -> Self {
        self.authz = Some(authz);
        self
    }

    async fn check(&self, cx: &SdkCallCx) -> Result<(), WorkflowError> {
        let Some(authz_repo) = self.authz.as_ref() else {
            return Ok(());
        };
        authz::script_gate(
            authz_repo.as_ref(),
            cx,
            Capability::AppInvoke(cx.app_id),
            || WorkflowError::Forbidden,
            WorkflowError::Backend,
        )
        .await
    }
}

#[async_trait]
impl WorkflowService for WorkflowServiceImpl {
    async fn start(
        &self,
        cx: &SdkCallCx,
        name: &str,
        input: serde_json::Value,
    ) -> Result<WorkflowRunId, WorkflowError> {
        self.check(cx).await?;
        let wf = get_workflow_by_name(&self.pool, cx.app_id, name)
            .await
            .map_err(|e| WorkflowError::Backend(e.to_string()))?
            .ok_or_else(|| WorkflowError::NotFound(name.to_string()))?;
        if !wf.enabled {
            return Err(WorkflowError::Disabled(name.to_string()));
        }
        let new = NewWorkflowRun {
            workflow_id: wf.id,
            app_id: cx.app_id,
            input,
            // Correlate the run's step logs under the caller's execution chain.
            root_execution_id: cx.root_execution_id.into_inner(),
            workflow_depth: 0,
            parent_run_id: None,
            parent_step_id: None,
        };
        start_run(&self.pool, new, &wf.definition)
            .await
            .map_err(|e| WorkflowError::Backend(e.to_string()))
    }
}
