//! Execution DTOs for API requests and responses

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use utoipa::{IntoParams, ToSchema};

use attune_common::models::enums::ExecutionStatus;
use attune_common::models::execution::WorkflowTaskMetadata;
use attune_common::repositories::execution::ExecutionWithRefs;

/// Request DTO for creating a manual execution
#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct CreateExecutionRequest {
    /// Action reference to execute
    #[schema(example = "slack.post_message")]
    pub action_ref: String,

    /// Execution parameters/configuration
    #[schema(value_type = Object, example = json!({"channel": "#alerts", "message": "Manual test"}))]
    pub parameters: Option<JsonValue>,

    /// Environment variables for this execution
    #[schema(value_type = Object, example = json!({"DEBUG": "true", "LOG_LEVEL": "info"}))]
    pub env_vars: Option<JsonValue>,
}

/// Response DTO for execution information
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ExecutionResponse {
    /// Execution ID
    #[schema(example = 1)]
    pub id: i64,

    /// Action ID (optional, may be null for ad-hoc executions)
    #[schema(example = 1)]
    pub action: Option<i64>,

    /// Action reference
    #[schema(example = "slack.post_message")]
    pub action_ref: String,

    /// Execution configuration/parameters
    #[schema(value_type = Object, example = json!({"channel": "#alerts", "message": "System error detected"}))]
    pub config: Option<JsonValue>,

    /// Parent execution ID (for nested/child executions)
    #[schema(example = 1)]
    pub parent: Option<i64>,

    /// Enforcement ID (rule enforcement that triggered this)
    #[schema(example = 1)]
    pub enforcement: Option<i64>,

    /// Identity ID that initiated this execution
    #[schema(example = 1)]
    pub executor: Option<i64>,

    /// Worker ID currently assigned to this execution
    #[schema(example = 1)]
    pub worker: Option<i64>,

    /// Execution status
    #[schema(example = "succeeded")]
    pub status: ExecutionStatus,

    /// Execution result/output
    #[schema(value_type = Object, example = json!({"message_id": "1234567890.123456"}))]
    pub result: Option<JsonValue>,

    /// When the execution actually started running (worker picked it up).
    /// Null if the execution hasn't started running yet.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[schema(example = "2024-01-13T10:31:00Z", nullable = true)]
    pub started_at: Option<DateTime<Utc>>,

    /// Workflow task metadata (only populated for workflow task executions)
    #[serde(skip_serializing_if = "Option::is_none")]
    #[schema(value_type = Option<Object>, nullable = true)]
    pub workflow_task: Option<WorkflowTaskMetadata>,

    /// Creation timestamp
    #[schema(example = "2024-01-13T10:30:00Z")]
    pub created: DateTime<Utc>,

    /// Last update timestamp
    #[schema(example = "2024-01-13T10:35:00Z")]
    pub updated: DateTime<Utc>,
}

/// Simplified execution response (for list endpoints)
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ExecutionSummary {
    /// Execution ID
    #[schema(example = 1)]
    pub id: i64,

    /// Action reference
    #[schema(example = "slack.post_message")]
    pub action_ref: String,

    /// Execution status
    #[schema(example = "succeeded")]
    pub status: ExecutionStatus,

    /// Parent execution ID
    #[schema(example = 1)]
    pub parent: Option<i64>,

    /// Enforcement ID
    #[schema(example = 1)]
    pub enforcement: Option<i64>,

    /// Rule reference (if triggered by a rule)
    #[schema(example = "core.on_timer")]
    pub rule_ref: Option<String>,

    /// Trigger reference (if triggered by a trigger)
    #[schema(example = "core.timer")]
    pub trigger_ref: Option<String>,

    /// When the execution actually started running (worker picked it up).
    /// Null if the execution hasn't started running yet.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[schema(example = "2024-01-13T10:31:00Z", nullable = true)]
    pub started_at: Option<DateTime<Utc>>,

    /// Workflow task metadata (only populated for workflow task executions)
    #[serde(skip_serializing_if = "Option::is_none")]
    #[schema(value_type = Option<Object>, nullable = true)]
    pub workflow_task: Option<WorkflowTaskMetadata>,

    /// Creation timestamp
    #[schema(example = "2024-01-13T10:30:00Z")]
    pub created: DateTime<Utc>,

    /// Last update timestamp
    #[schema(example = "2024-01-13T10:35:00Z")]
    pub updated: DateTime<Utc>,
}

/// Query parameters for filtering executions
#[derive(Debug, Clone, Deserialize, IntoParams)]
pub struct ExecutionQueryParams {
    /// Filter by execution status
    #[param(example = "succeeded")]
    pub status: Option<ExecutionStatus>,

    /// Filter by action reference
    #[param(example = "slack.post_message")]
    pub action_ref: Option<String>,

    /// Filter by pack name
    #[param(example = "core")]
    pub pack_name: Option<String>,

    /// Filter by rule reference
    #[param(example = "core.on_timer")]
    pub rule_ref: Option<String>,

    /// Filter by trigger reference
    #[param(example = "core.timer")]
    pub trigger_ref: Option<String>,

    /// Filter by executor ID
    #[param(example = 1)]
    pub executor: Option<i64>,

    /// Search in result JSON (case-insensitive substring match)
    #[param(example = "error")]
    pub result_contains: Option<String>,

    /// Filter by enforcement ID
    #[param(example = 1)]
    pub enforcement: Option<i64>,

    /// Filter by parent execution ID
    #[param(example = 1)]
    pub parent: Option<i64>,

    /// If true, only return top-level executions (those without a parent).
    /// Useful for the "By Workflow" view where child tasks are loaded separately.
    #[serde(default)]
    #[param(example = false)]
    pub top_level_only: Option<bool>,

    /// Page number (for pagination)
    #[serde(default = "default_page")]
    #[param(example = 1, minimum = 1)]
    pub page: u32,

    /// Items per page (for pagination)
    #[serde(default = "default_per_page")]
    #[param(example = 50, minimum = 1, maximum = 100)]
    pub per_page: u32,
}

impl ExecutionQueryParams {
    /// Get the SQL offset value
    pub fn offset(&self) -> u32 {
        (self.page.saturating_sub(1)) * self.per_page
    }

    /// Get the limit value (with max cap)
    pub fn limit(&self) -> u32 {
        self.per_page.min(100)
    }
}

/// Convert from Execution model to ExecutionResponse
impl From<attune_common::models::execution::Execution> for ExecutionResponse {
    fn from(execution: attune_common::models::execution::Execution) -> Self {
        Self {
            id: execution.id,
            action: execution.action,
            action_ref: execution.action_ref,
            config: execution
                .config
                .map(|c| serde_json::to_value(c).unwrap_or(JsonValue::Null)),
            parent: execution.parent,
            enforcement: execution.enforcement,
            executor: execution.executor,
            worker: execution.worker,
            status: execution.status,
            result: execution
                .result
                .map(|r| serde_json::to_value(r).unwrap_or(JsonValue::Null)),
            started_at: execution.started_at,
            workflow_task: execution.workflow_task,
            created: execution.created,
            updated: execution.updated,
        }
    }
}

/// Convert from Execution model to ExecutionSummary
impl From<attune_common::models::execution::Execution> for ExecutionSummary {
    fn from(execution: attune_common::models::execution::Execution) -> Self {
        Self {
            id: execution.id,
            action_ref: execution.action_ref,
            status: execution.status,
            parent: execution.parent,
            enforcement: execution.enforcement,
            rule_ref: None,    // Populated separately via enforcement lookup
            trigger_ref: None, // Populated separately via enforcement lookup
            started_at: execution.started_at,
            workflow_task: execution.workflow_task,
            created: execution.created,
            updated: execution.updated,
        }
    }
}

/// Convert from the joined query result (execution + enforcement refs).
/// `rule_ref` and `trigger_ref` are already populated from the SQL JOIN.
impl From<ExecutionWithRefs> for ExecutionSummary {
    fn from(row: ExecutionWithRefs) -> Self {
        Self {
            id: row.id,
            action_ref: row.action_ref,
            status: row.status,
            parent: row.parent,
            enforcement: row.enforcement,
            rule_ref: row.rule_ref,
            trigger_ref: row.trigger_ref,
            started_at: row.started_at,
            workflow_task: row.workflow_task,
            created: row.created,
            updated: row.updated,
        }
    }
}

fn default_page() -> u32 {
    1
}

fn default_per_page() -> u32 {
    20
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_query_params_defaults() {
        let json = r#"{}"#;
        let params: ExecutionQueryParams = serde_json::from_str(json).unwrap();
        assert_eq!(params.page, 1);
        assert_eq!(params.per_page, 20);
        assert!(params.status.is_none());
    }

    #[test]
    fn test_query_params_with_filters() {
        let json = r#"{
            "status": "completed",
            "action_ref": "test.action",
            "page": 2,
            "per_page": 50
        }"#;
        let params: ExecutionQueryParams = serde_json::from_str(json).unwrap();
        assert_eq!(params.page, 2);
        assert_eq!(params.per_page, 50);
        assert_eq!(params.status, Some(ExecutionStatus::Completed));
        assert_eq!(params.action_ref, Some("test.action".to_string()));
    }

    #[test]
    fn test_query_params_offset() {
        let params = ExecutionQueryParams {
            status: None,
            action_ref: None,
            enforcement: None,
            parent: None,
            top_level_only: None,
            pack_name: None,
            rule_ref: None,
            trigger_ref: None,
            executor: None,
            result_contains: None,
            page: 3,
            per_page: 20,
        };
        assert_eq!(params.offset(), 40); // (3-1) * 20
    }

    #[test]
    fn test_query_params_limit_cap() {
        let params = ExecutionQueryParams {
            status: None,
            action_ref: None,
            enforcement: None,
            parent: None,
            top_level_only: None,
            pack_name: None,
            rule_ref: None,
            trigger_ref: None,
            executor: None,
            result_contains: None,
            page: 1,
            per_page: 200, // Exceeds max
        };
        assert_eq!(params.limit(), 100); // Capped at 100
    }
}
