//! Core trait for ecosystem integrations

use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Error type for ecosystem integration operations
#[derive(Debug, Error)]
pub enum EcosystemIntegrationError {
    #[error("Integration '{name}' not found")]
    NotFound { name: String },

    #[error("Integration '{name}' is disabled")]
    Disabled { name: String },

    #[error("Action '{action}' not supported by '{integration}'")]
    ActionNotSupported { integration: String, action: String },

    #[error("Serialization error: {0}")]
    SerializationError(#[from] serde_json::Error),

    #[error("Configuration error: {0}")]
    ConfigError(String),

    #[error("Integration error: {0}")]
    IntegrationError(String),

    #[error("Task not found: {0}")]
    TaskNotFound(String),

    #[error("Phase transition not allowed from {from} to {to}")]
    InvalidPhaseTransition { from: String, to: String },
}

/// Information about a Syntaxis task for integration purposes
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskInfo {
    pub id: String,
    pub description: String,
    pub task_name: Option<String>,
    pub task_type: String,
    pub task_priority: String,
    pub task_state: String,
    pub completed: bool,
    pub phase: String,
    pub actions: Option<Vec<serde_json::Value>>,
}

/// Phase information for transitions
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhaseInfo {
    pub name: String,
    pub description: Option<String>,
    pub order: u32,
}

/// Core trait for ecosystem integrations
///
/// Implementations should be stateless or use Arc for shared state.
/// All methods should be idempotent where possible.
#[async_trait::async_trait]
pub trait EcosystemIntegration: Send + Sync {
    /// Name of this integration (e.g., "gitops", "valida", "audit")
    fn name(&self) -> &str;

    /// Optional description of what this integration does
    fn description(&self) -> Option<&str> {
        None
    }

    /// Check if this integration is available/configured
    fn is_available(&self) -> bool {
        true
    }

    /// Called when a new task is created in Syntaxis
    async fn on_task_created(&self, task: &TaskInfo) -> Result<(), EcosystemIntegrationError> {
        let _ = task;
        Ok(())
    }

    /// Called when a task state changes
    async fn on_task_state_changed(
        &self,
        task: &TaskInfo,
        old_state: &str,
        new_state: &str,
    ) -> Result<(), EcosystemIntegrationError> {
        let _ = (task, old_state, new_state);
        Ok(())
    }

    /// Called when a task is completed
    async fn on_task_completed(&self, task: &TaskInfo) -> Result<(), EcosystemIntegrationError> {
        let _ = task;
        Ok(())
    }

    /// Called when a task is blocked/failed
    async fn on_task_blocked(
        &self,
        task: &TaskInfo,
        reason: &str,
    ) -> Result<(), EcosystemIntegrationError> {
        let _ = (task, reason);
        Ok(())
    }

    /// Called before a phase transition
    ///
    /// Return an error to prevent the transition
    async fn pre_phase_transition(
        &self,
        from: &PhaseInfo,
        to: &PhaseInfo,
    ) -> Result<(), EcosystemIntegrationError> {
        let _ = (from, to);
        Ok(())
    }

    /// Called after a successful phase transition
    async fn post_phase_transition(
        &self,
        from: &PhaseInfo,
        to: &PhaseInfo,
    ) -> Result<(), EcosystemIntegrationError> {
        let _ = (from, to);
        Ok(())
    }

    /// Execute a task action
    ///
    /// Actions can come from task_actions field or from manual triggers
    async fn execute_action(
        &self,
        action_type: &str,
        _config: &serde_json::Value,
    ) -> Result<serde_json::Value, EcosystemIntegrationError> {
        Err(EcosystemIntegrationError::ActionNotSupported {
            integration: self.name().to_string(),
            action: action_type.to_string(),
        })
    }

    /// Get supported action types
    fn supported_actions(&self) -> Vec<String> {
        vec![]
    }

    /// Validate configuration for this integration
    fn validate_config(&self, config: &serde_json::Value) -> Result<(), EcosystemIntegrationError> {
        let _ = config;
        Ok(())
    }

    /// Get integration metadata
    fn metadata(&self) -> IntegrationMetadata {
        IntegrationMetadata {
            name: self.name().to_string(),
            description: self.description().map(|s| s.to_string()),
            version: crate::VERSION.to_string(),
            supported_actions: self.supported_actions(),
        }
    }
}

/// Metadata about an integration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IntegrationMetadata {
    pub name: String,
    pub description: Option<String>,
    pub version: String,
    pub supported_actions: Vec<String>,
}

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

    struct TestIntegration;

    #[async_trait::async_trait]
    impl EcosystemIntegration for TestIntegration {
        fn name(&self) -> &str {
            "test"
        }
    }

    #[test]
    fn test_integration_metadata() {
        let integration = TestIntegration;
        let metadata = integration.metadata();
        assert_eq!(metadata.name, "test");
    }

    #[tokio::test]
    async fn test_default_implementations() {
        let integration = TestIntegration;

        let task = TaskInfo {
            id: "test".to_string(),
            description: "Test task".to_string(),
            task_name: None,
            task_type: "test".to_string(),
            task_priority: "medium".to_string(),
            task_state: "planned".to_string(),
            completed: false,
            phase: "devel".to_string(),
            actions: None,
        };

        // All default implementations should succeed
        assert!(integration.on_task_created(&task).await.is_ok());
        assert!(integration.on_task_completed(&task).await.is_ok());
        assert!(integration
            .on_task_state_changed(&task, "planned", "in_progress")
            .await
            .is_ok());

        let from = PhaseInfo {
            name: "devel".to_string(),
            description: None,
            order: 1,
        };
        let to = PhaseInfo {
            name: "review".to_string(),
            description: None,
            order: 2,
        };

        assert!(integration.pre_phase_transition(&from, &to).await.is_ok());
        assert!(integration.post_phase_transition(&from, &to).await.is_ok());
    }
}
