//! Error types for the orchestrator plugin.
//!
//! This module provides structured error handling with specific error kinds
//! for different failure scenarios in orchestrator operations.

use std::fmt;

/// Enum representing different kinds of orchestrator errors.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OrchestratorErrorKind {
    /// Task not found
    TaskNotFound,
    /// Workflow not found
    WorkflowNotFound,
    /// Validation failed
    ValidationFailed,
    /// Task submission failed
    SubmissionFailed,
    /// Task already exists
    TaskAlreadyExists,
    /// Invalid task status
    InvalidTaskStatus,
    /// Data directory not found
    DataDirNotFound,
    /// File read/write error
    FileError,
    /// JSON parsing error
    ParseError,
    /// KCL validation error
    KclError,
    /// Orchestrator not running
    OrchestratorNotRunning,
    /// Timeout waiting for task
    Timeout,
    /// Configuration error
    ConfigurationError,
    /// Internal error
    InternalError,
}

impl fmt::Display for OrchestratorErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::TaskNotFound => write!(f, "task not found"),
            Self::WorkflowNotFound => write!(f, "workflow not found"),
            Self::ValidationFailed => write!(f, "validation failed"),
            Self::SubmissionFailed => write!(f, "submission failed"),
            Self::TaskAlreadyExists => write!(f, "task already exists"),
            Self::InvalidTaskStatus => write!(f, "invalid task status"),
            Self::DataDirNotFound => write!(f, "data directory not found"),
            Self::FileError => write!(f, "file error"),
            Self::ParseError => write!(f, "parse error"),
            Self::KclError => write!(f, "KCL error"),
            Self::OrchestratorNotRunning => write!(f, "orchestrator not running"),
            Self::Timeout => write!(f, "timeout"),
            Self::ConfigurationError => write!(f, "configuration error"),
            Self::InternalError => write!(f, "internal error"),
        }
    }
}

/// Structured error type for orchestrator operations.
#[derive(Debug)]
pub struct OrchestratorError {
    /// The kind of error that occurred
    pub kind: OrchestratorErrorKind,
    /// Additional context about the error
    pub context: String,
    /// Optional underlying error
    pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
}

impl OrchestratorError {
    /// Creates a new OrchestratorError with the specified kind and context.
    pub fn new(kind: OrchestratorErrorKind, context: impl Into<String>) -> Self {
        Self {
            kind,
            context: context.into(),
            source: None,
        }
    }

    /// Creates an OrchestratorError with an underlying source error.
    pub fn with_source(
        kind: OrchestratorErrorKind,
        context: impl Into<String>,
        source: impl std::error::Error + Send + Sync + 'static,
    ) -> Self {
        Self {
            kind,
            context: context.into(),
            source: Some(Box::new(source)),
        }
    }

    /// Creates a task not found error.
    pub fn task_not_found(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::TaskNotFound, context)
    }

    /// Creates a workflow not found error.
    pub fn workflow_not_found(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::WorkflowNotFound, context)
    }

    /// Creates a validation failed error.
    pub fn validation_failed(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::ValidationFailed, context)
    }

    /// Creates a submission failed error.
    pub fn submission_failed(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::SubmissionFailed, context)
    }

    /// Creates a data directory not found error.
    pub fn data_dir_not_found(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::DataDirNotFound, context)
    }

    /// Creates a file error.
    pub fn file_error(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::FileError, context)
    }

    /// Creates a parse error.
    pub fn parse_error(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::ParseError, context)
    }

    /// Creates a KCL error.
    pub fn kcl_error(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::KclError, context)
    }

    /// Creates an orchestrator not running error.
    pub fn orchestrator_not_running(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::OrchestratorNotRunning, context)
    }

    /// Creates a timeout error.
    pub fn timeout(context: impl Into<String>) -> Self {
        Self::new(OrchestratorErrorKind::Timeout, context)
    }
}

impl fmt::Display for OrchestratorError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}: {}", self.kind, self.context)?;
        if let Some(ref source) = self.source {
            write!(f, " (caused by: {})", source)?;
        }
        Ok(())
    }
}

impl std::error::Error for OrchestratorError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        self.source
            .as_ref()
            .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
    }
}

impl From<OrchestratorError> for nu_protocol::LabeledError {
    fn from(err: OrchestratorError) -> Self {
        nu_protocol::LabeledError::new(err.to_string())
    }
}

impl From<OrchestratorError> for String {
    fn from(err: OrchestratorError) -> Self {
        err.to_string()
    }
}

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

    #[test]
    fn test_orchestrator_error_display() {
        let error = OrchestratorError::new(OrchestratorErrorKind::TaskNotFound, "task-123");
        assert!(error.to_string().contains("task not found"));
        assert!(error.to_string().contains("task-123"));
    }

    #[test]
    fn test_orchestrator_error_with_source() {
        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
        let error = OrchestratorError::with_source(
            OrchestratorErrorKind::FileError,
            "failed to read task file",
            io_error,
        );
        assert!(error.to_string().contains("caused by"));
    }

    #[test]
    fn test_convenience_constructors() {
        let error = OrchestratorError::task_not_found("test");
        assert_eq!(error.kind, OrchestratorErrorKind::TaskNotFound);

        let error = OrchestratorError::validation_failed("test");
        assert_eq!(error.kind, OrchestratorErrorKind::ValidationFailed);

        let error = OrchestratorError::kcl_error("test");
        assert_eq!(error.kind, OrchestratorErrorKind::KclError);
    }
}
