//! Pipeline-related data models.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

/// Basic pipeline information returned from list operations.
///
/// This is a lighter version of [`Pipeline`] with fewer fields,
/// typically used when listing multiple pipelines.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PipelineBasic {
    /// Unique pipeline identifier
    pub id: u64,

    /// Git reference (branch or tag) the pipeline ran on
    #[serde(rename = "ref")]
    pub ref_name: String,

    /// Current status of the pipeline
    pub status: PipelineStatus,

    /// Source that triggered the pipeline (push, web, api, etc.)
    pub source: Option<String>,

    /// When the pipeline was created
    pub created_at: DateTime<Utc>,

    /// When the pipeline was last updated
    pub updated_at: DateTime<Utc>,

    /// Web URL to view the pipeline in GitLab UI
    pub web_url: String,
}

/// Detailed pipeline information.
///
/// This includes all fields from [`PipelineBasic`] plus additional
/// details like duration, user information, and more.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Pipeline {
    /// Unique pipeline identifier
    pub id: u64,

    /// Internal ID (project-specific)
    pub iid: Option<u64>,

    /// Git reference (branch or tag) the pipeline ran on
    #[serde(rename = "ref")]
    pub ref_name: String,

    /// Current status of the pipeline
    pub status: PipelineStatus,

    /// Source that triggered the pipeline
    pub source: Option<String>,

    /// Git commit SHA that the pipeline ran on
    pub sha: String,

    /// Short SHA (first 8 characters)
    #[serde(default)]
    pub short_sha: Option<String>,

    /// When the pipeline was created
    pub created_at: DateTime<Utc>,

    /// When the pipeline was last updated
    pub updated_at: DateTime<Utc>,

    /// When the pipeline started running
    pub started_at: Option<DateTime<Utc>>,

    /// When the pipeline finished (success or failure)
    pub finished_at: Option<DateTime<Utc>>,

    /// Duration of the pipeline execution in seconds
    pub duration: Option<f64>,

    /// Time spent in queued state before execution
    pub queued_duration: Option<f64>,

    /// Web URL to view the pipeline in GitLab UI
    pub web_url: String,

    /// Coverage percentage (if coverage reporting is enabled)
    pub coverage: Option<String>,

    /// User who triggered the pipeline
    #[serde(default)]
    pub user: Option<PipelineUser>,

    /// Whether this pipeline was triggered
    #[serde(default)]
    pub triggered: bool,
}

/// User information associated with a pipeline.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PipelineUser {
    /// User ID
    pub id: u64,

    /// Username
    pub username: String,

    /// Display name
    pub name: String,

    /// User state (active, blocked, etc.)
    pub state: String,

    /// URL to user's avatar image
    pub avatar_url: Option<String>,

    /// Web URL to user's profile
    pub web_url: String,
}

/// Status of a pipeline.
///
/// Represents the various states a GitLab pipeline can be in during its lifecycle.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum PipelineStatus {
    /// Pipeline created but not yet running
    Created,

    /// Pipeline is waiting to be picked up
    Pending,

    /// Pipeline is preparing to run (downloading artifacts, etc.)
    Preparing,

    /// Pipeline is currently running
    Running,

    /// Pipeline completed successfully
    Success,

    /// Pipeline failed
    Failed,

    /// Pipeline was canceled
    Canceled,

    /// Pipeline was skipped
    Skipped,

    /// Pipeline is waiting for manual action
    Manual,

    /// Pipeline is scheduled to run
    Scheduled,
}

impl PipelineStatus {
    /// Returns `true` if the pipeline is in a terminal state (completed, won't change).
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_gitlab::models::PipelineStatus;
    ///
    /// assert!(PipelineStatus::Success.is_finished());
    /// assert!(PipelineStatus::Failed.is_finished());
    /// assert!(!PipelineStatus::Running.is_finished());
    /// ```
    pub fn is_finished(self) -> bool {
        matches!(
            self,
            Self::Success | Self::Failed | Self::Canceled | Self::Skipped
        )
    }

    /// Returns `true` if the pipeline is currently active (running or pending).
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_gitlab::models::PipelineStatus;
    ///
    /// assert!(PipelineStatus::Running.is_active());
    /// assert!(PipelineStatus::Pending.is_active());
    /// assert!(!PipelineStatus::Success.is_active());
    /// ```
    pub fn is_active(self) -> bool {
        matches!(
            self,
            Self::Created | Self::Pending | Self::Preparing | Self::Running
        )
    }

    /// Returns `true` if the pipeline succeeded.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_gitlab::models::PipelineStatus;
    ///
    /// assert!(PipelineStatus::Success.is_successful());
    /// assert!(!PipelineStatus::Failed.is_successful());
    /// ```
    pub fn is_successful(self) -> bool {
        self == Self::Success
    }

    /// Returns `true` if the pipeline failed.
    ///
    /// # Examples
    ///
    /// ```
    /// use lmrc_gitlab::models::PipelineStatus;
    ///
    /// assert!(PipelineStatus::Failed.is_failed());
    /// assert!(!PipelineStatus::Success.is_failed());
    /// ```
    pub fn is_failed(self) -> bool {
        self == Self::Failed
    }
}

impl std::fmt::Display for PipelineStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Created => write!(f, "created"),
            Self::Pending => write!(f, "pending"),
            Self::Preparing => write!(f, "preparing"),
            Self::Running => write!(f, "running"),
            Self::Success => write!(f, "success"),
            Self::Failed => write!(f, "failed"),
            Self::Canceled => write!(f, "canceled"),
            Self::Skipped => write!(f, "skipped"),
            Self::Manual => write!(f, "manual"),
            Self::Scheduled => write!(f, "scheduled"),
        }
    }
}

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

    #[test]
    fn test_pipeline_status_is_finished() {
        assert!(PipelineStatus::Success.is_finished());
        assert!(PipelineStatus::Failed.is_finished());
        assert!(PipelineStatus::Canceled.is_finished());
        assert!(PipelineStatus::Skipped.is_finished());

        assert!(!PipelineStatus::Running.is_finished());
        assert!(!PipelineStatus::Pending.is_finished());
    }

    #[test]
    fn test_pipeline_status_is_active() {
        assert!(PipelineStatus::Running.is_active());
        assert!(PipelineStatus::Pending.is_active());
        assert!(PipelineStatus::Created.is_active());
        assert!(PipelineStatus::Preparing.is_active());

        assert!(!PipelineStatus::Success.is_active());
        assert!(!PipelineStatus::Failed.is_active());
    }

    #[test]
    fn test_pipeline_status_display() {
        assert_eq!(PipelineStatus::Success.to_string(), "success");
        assert_eq!(PipelineStatus::Failed.to_string(), "failed");
        assert_eq!(PipelineStatus::Running.to_string(), "running");
    }

    #[test]
    fn test_pipeline_status_serialization() {
        let status = PipelineStatus::Success;
        let json = serde_json::to_string(&status).unwrap();
        assert_eq!(json, "\"success\"");

        let deserialized: PipelineStatus = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized, PipelineStatus::Success);
    }
}
