//! Trait definitions for testable observer system architecture.
//!
//! These traits define the boundaries between components, enabling
//! mock implementations for testing without external dependencies.

use std::future::Future;

use uuid::Uuid;

use crate::{config::ActionConfig, error::Result, event::EntityEvent};

/// Event source abstraction for testing
///
/// Allows tests to provide pre-defined events without database connectivity.
#[allow(async_fn_in_trait)] // Reason: trait is used with concrete types only, not dyn Trait
pub trait EventSource: Send + Sync {
    /// Get the next event from the source
    ///
    /// Returns None when the source is exhausted
    async fn next_event(&mut self) -> Option<EntityEvent>;
}

/// Action execution abstraction for testing
///
/// Enables testing action execution without real external services.
pub trait ActionExecutor: Send + Sync {
    /// Execute an action on an event
    ///
    /// # Arguments
    /// * `event` - The triggering entity event
    /// * `action` - The action configuration to execute
    ///
    /// # Returns
    /// Result with action result on success or error
    fn execute<'a>(
        &'a self,
        event: &'a EntityEvent,
        action: &'a ActionConfig,
    ) -> impl Future<Output = Result<ActionResult>> + Send + 'a;
}

/// Result of executing an action
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ActionResult {
    /// Type of action that was executed
    pub action_type: String,
    /// Whether the action succeeded
    pub success:     bool,
    /// Status message
    pub message:     String,
    /// Execution time in milliseconds
    pub duration_ms: f64,
}

impl ActionResult {
    /// Creates a new `ActionResult`.
    #[must_use]
    pub const fn new(
        action_type: String,
        success: bool,
        message: String,
        duration_ms: f64,
    ) -> Self {
        Self {
            action_type,
            success,
            message,
            duration_ms,
        }
    }
}

/// Dead letter queue abstraction for testing
///
/// Allows testing failed action storage without database.
#[async_trait::async_trait]
pub trait DeadLetterQueue: Send + Sync {
    /// Add a failed action to the DLQ
    ///
    /// # Arguments
    /// * `event` - The event that triggered the action
    /// * `action` - The action that failed
    /// * `error` - The error message
    ///
    /// # Returns
    /// UUID of the DLQ item
    async fn push(&self, event: EntityEvent, action: ActionConfig, error: String) -> Result<Uuid>;

    /// Get pending DLQ items
    ///
    /// # Arguments
    /// * `limit` - Maximum number of items to return
    async fn get_pending(&self, limit: i64) -> Result<Vec<DlqItem>>;

    /// Mark a DLQ item as successfully processed
    async fn mark_success(&self, id: Uuid) -> Result<()>;

    /// Mark a DLQ item as permanently failed
    async fn mark_retry_failed(&self, id: Uuid, error: &str) -> Result<()>;

    /// Route an unparseable raw event payload to the dead letter queue.
    ///
    /// Called when event deserialization fails — no `EntityEvent` is available.
    /// The default implementation logs a warning and discards the bytes;
    /// override this method to persist the raw payload for later inspection.
    ///
    /// # Arguments
    /// * `raw`    — raw bytes that could not be deserialized
    /// * `reason` — the deserialization error message
    async fn push_raw(&self, raw: &[u8], reason: &str) -> Result<()> {
        tracing::warn!(
            bytes = raw.len(),
            %reason,
            "Unparseable event dropped (this DLQ implementation does not support raw storage)"
        );
        Ok(())
    }
}

/// Item in the dead letter queue
#[derive(Debug, Clone)]
pub struct DlqItem {
    /// Unique identifier
    pub id:            Uuid,
    /// The event that failed
    pub event:         EntityEvent,
    /// The action configuration
    pub action:        ActionConfig,
    /// The error message
    pub error_message: String,
    /// Number of retry attempts
    pub attempts:      u32,
}

/// Condition evaluator abstraction for testing
///
/// Enables testing condition logic without parsing overhead.
pub trait ConditionEvaluator: Send + Sync {
    /// Evaluate a condition expression against an event
    ///
    /// # Arguments
    /// * `condition` - The condition expression (e.g., "status == 'shipped'")
    /// * `event` - The event to evaluate against
    ///
    /// # Errors
    ///
    /// Returns `ObserverError` if the condition expression is syntactically invalid
    /// or references a non-existent field.
    fn evaluate(&self, condition: &str, event: &EntityEvent) -> Result<bool>;
}

/// Template renderer abstraction for testing
///
/// Enables testing template rendering without template engine.
pub trait TemplateRenderer: Send + Sync {
    /// Render a template with event data
    ///
    /// # Arguments
    /// * `template` - The template string (Jinja-style: {{ field }})
    /// * `data` - The data to render with
    ///
    /// # Errors
    ///
    /// Returns `ObserverError` if the template syntax is invalid or rendering fails.
    fn render(&self, template: &str, data: &serde_json::Value) -> Result<String>;
}
