//! Single-step execution for saga forward phase.

use std::time::Instant;

use ::tracing::{debug, info, warn};
use uuid::Uuid;

use super::{SagaExecutor, StepExecutionResult};
use crate::saga_store::{Result as SagaStoreResult, StepState};

impl SagaExecutor {
    /// Execute a single saga step
    ///
    /// Executes a single mutation step within a saga, handling:
    /// - Step state validation (Pending → Executing → Completed)
    /// - `@requires` field pre-fetching from owning subgraphs
    /// - Entity data augmentation with required fields
    /// - Mutation execution via `MutationExecutor`
    /// - Result capture and persistence
    ///
    /// # Arguments
    ///
    /// * `saga_id` - ID of saga being executed
    /// * `step_number` - Step number to execute (1-indexed, 1 = first step)
    /// * `mutation_name` - GraphQL mutation operation name
    /// * `variables` - Input variables for the mutation (JSON value)
    /// * `subgraph` - Target subgraph name (must exist in federation)
    ///
    /// # Returns
    ///
    /// `StepExecutionResult` with:
    /// - `success`: true if step executed successfully
    /// - `data`: Result entity data if successful
    /// - `error`: Error description if failed
    /// - `duration_ms`: Execution time for monitoring
    ///
    /// # Errors
    ///
    /// Returns `SagaStoreError` if:
    /// - Saga not found in store
    /// - Step already executed (not in Pending state)
    /// - Subgraph unavailable
    /// - Mutation execution fails
    /// - @requires fields cannot be fetched
    ///
    /// # Example
    ///
    /// ```text
    /// // Requires: distributed saga infrastructure (PostgreSQL + message broker).
    /// // See: tests/integration/ for runnable examples.
    /// let executor = SagaExecutor::new();
    /// let result = executor.execute_step(
    ///     saga_id,
    ///     1,
    ///     "createOrder",
    ///     &json!({"customerId": "c123", "total": 100.0}),
    ///     "orders-service"
    /// ).await?;
    ///
    /// if result.success {
    ///     println!("Order created with data: {:?}", result.data);
    /// } else {
    ///     eprintln!("Step failed: {}", result.error.unwrap());
    /// }
    /// ```
    #[allow(clippy::cognitive_complexity)] // Reason: sequential step execution with @requires pre-fetch, store validation, mutation dispatch, and result persistence
    pub async fn execute_step(
        &self,
        saga_id: Uuid,
        step_number: u32,
        mutation_name: &str,
        variables: &serde_json::Value,
        subgraph: &str,
    ) -> SagaStoreResult<StepExecutionResult> {
        let start_time = Instant::now();

        info!(
            saga_id = %saga_id,
            step = step_number,
            mutation = mutation_name,
            subgraph = subgraph,
            "Step execution started"
        );

        // Pre-fetch @requires fields and augment input entity before any mutation dispatch.
        // This runs regardless of whether a saga store is present so the wiring is always active.
        let requires_fields = self.pre_fetch_requires_fields(saga_id, step_number).await?;
        let augmented_variables =
            self.augment_entity_with_requires(variables.clone(), requires_fields);

        // 1. Validate step exists in saga (if store is available)
        if let Some(store) = &self.store {
            // Load saga to verify it exists
            let saga = store.load_saga(saga_id).await.map_err(|e| {
                warn!(saga_id = %saga_id, error = ?e, "Failed to load saga");
                e
            })?;

            if saga.is_none() {
                return Err(crate::saga_store::SagaStoreError::SagaNotFound(saga_id));
            }

            // Load all steps for this saga
            let steps = store.load_saga_steps(saga_id).await.map_err(|e| {
                warn!(saga_id = %saga_id, error = ?e, "Failed to load saga steps");
                e
            })?;

            // Find the step we're executing
            let step_id = Uuid::new_v4(); // Placeholder ID for error reporting
            let saga_step = steps
                .iter()
                .find(|s| s.order == step_number as usize)
                .ok_or(crate::saga_store::SagaStoreError::StepNotFound(step_id))?;

            // 2. Check step state is Pending
            if saga_step.state != StepState::Pending {
                return Err(crate::saga_store::SagaStoreError::InvalidStateTransition {
                    from: format!("{:?}", saga_step.state),
                    to:   "Executing".to_string(),
                });
            }

            // 3. Transition step to Executing
            store
                .update_saga_step_state(saga_step.id, &StepState::Executing)
                .await
                .map_err(|e| {
                    warn!(saga_id = %saga_id, step = step_number, error = ?e, "Failed to transition step to Executing");
                    e
                })?;

            info!(saga_id = %saga_id, step = step_number, "Step transitioned to Executing");

            // 4. Execute mutation via MutationExecutor (placeholder implementation).
            // `augmented_variables` was pre-computed above with @requires fields applied.
            let result_data = serde_json::json!({
                "__typename": saga_step.typename,
                "id": format!("entity-{}-step-{}", saga_id, step_number),
                mutation_name: "executed",
                "input": augmented_variables,
            });

            // 5. Capture result data and transition step to Completed
            store
                .update_saga_step_result(saga_step.id, &result_data)
                .await
                .map_err(|e| {
                    warn!(saga_id = %saga_id, step = step_number, error = ?e, "Failed to save step result");
                    e
                })?;

            // 6. Transition step to Completed
            store
                .update_saga_step_state(saga_step.id, &StepState::Completed)
                .await
                .map_err(|e| {
                    warn!(saga_id = %saga_id, step = step_number, error = ?e, "Failed to transition step to Completed");
                    e
                })?;

            info!(saga_id = %saga_id, step = step_number, "Step transitioned to Completed");

            #[allow(clippy::cast_possible_truncation)]
            // Reason: duration millis won't exceed u64 in practice
            let duration_ms = start_time.elapsed().as_millis() as u64;

            let result = StepExecutionResult {
                step_number,
                success: true,
                data: Some(result_data),
                error: None,
                duration_ms,
            };

            info!(
                saga_id = %saga_id,
                step = step_number,
                duration_ms = result.duration_ms,
                "Step execution completed successfully"
            );

            Ok(result)
        } else {
            // No store available - return placeholder success for testing
            debug!("No saga store available - returning placeholder result");

            #[allow(clippy::cast_possible_truncation)]
            // Reason: duration millis won't exceed u64 in practice
            let duration_ms = start_time.elapsed().as_millis() as u64;

            let result = StepExecutionResult {
                step_number,
                success: true,
                data: Some(serde_json::json!({
                    "__typename": "Entity",
                    "id": format!("entity-{}", step_number),
                    mutation_name: "ok",
                    "input": augmented_variables,
                })),
                error: None,
                duration_ms,
            };

            info!(
                saga_id = %saga_id,
                step = step_number,
                duration_ms = result.duration_ms,
                "Step execution completed (no store)"
            );

            Ok(result)
        }
    }
}
