//! Core types for governance workflows.
//!
//! These types represent approval requests, decisions, and status tracking
//! for pattern promotion governance.

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

/// Status of an approval request.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", content = "data", rename_all = "snake_case")]
pub enum ApprovalStatus {
    /// Request is pending at a specific stage.
    Pending {
        /// Current stage name.
        stage: String,
    },
    /// Request has been fully approved.
    Approved,
    /// Request was rejected at a specific stage.
    Rejected {
        /// Stage where rejection occurred.
        stage: String,
        /// Reason for rejection.
        reason: String,
    },
    /// Request was escalated from one stage to another.
    Escalated {
        /// Stage escalated from.
        from_stage: String,
        /// Stage escalated to.
        to_stage: String,
    },
    /// Request expired due to timeout.
    Expired,
}

impl ApprovalStatus {
    /// Returns true if the request is still pending.
    pub fn is_pending(&self) -> bool {
        matches!(self, ApprovalStatus::Pending { .. })
    }

    /// Returns true if the request is approved.
    pub fn is_approved(&self) -> bool {
        matches!(self, ApprovalStatus::Approved)
    }

    /// Returns true if the request is rejected.
    pub fn is_rejected(&self) -> bool {
        matches!(self, ApprovalStatus::Rejected { .. })
    }

    /// Returns true if the request is terminal (approved, rejected, or expired).
    pub fn is_terminal(&self) -> bool {
        matches!(
            self,
            ApprovalStatus::Approved | ApprovalStatus::Rejected { .. } | ApprovalStatus::Expired
        )
    }

    /// Get the current stage name if pending.
    pub fn current_stage(&self) -> Option<&str> {
        match self {
            ApprovalStatus::Pending { stage } => Some(stage),
            _ => None,
        }
    }

    /// Display name for the status.
    pub fn display_name(&self) -> &'static str {
        match self {
            ApprovalStatus::Pending { .. } => "Pending",
            ApprovalStatus::Approved => "Approved",
            ApprovalStatus::Rejected { .. } => "Rejected",
            ApprovalStatus::Escalated { .. } => "Escalated",
            ApprovalStatus::Expired => "Expired",
        }
    }
}

impl Default for ApprovalStatus {
    fn default() -> Self {
        ApprovalStatus::Pending { stage: String::new() }
    }
}

impl std::fmt::Display for ApprovalStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ApprovalStatus::Pending { stage } => write!(f, "Pending ({})", stage),
            ApprovalStatus::Approved => write!(f, "Approved"),
            ApprovalStatus::Rejected { stage, reason } => {
                write!(f, "Rejected at {}: {}", stage, reason)
            }
            ApprovalStatus::Escalated { from_stage, to_stage } => {
                write!(f, "Escalated {} → {}", from_stage, to_stage)
            }
            ApprovalStatus::Expired => write!(f, "Expired"),
        }
    }
}

/// Decision type for a stage.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Decision {
    /// Stage was approved.
    Approved,
    /// Stage was rejected.
    Rejected,
}

impl std::fmt::Display for Decision {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Decision::Approved => write!(f, "approved"),
            Decision::Rejected => write!(f, "rejected"),
        }
    }
}

/// An approval decision record (audit trail).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApprovalDecision {
    /// Unique identifier for this decision.
    pub id: Uuid,
    /// The request this decision belongs to.
    pub request_id: Uuid,
    /// Stage where this decision was made.
    pub stage: String,
    /// The decision made (approved or rejected).
    pub decision: Decision,
    /// Who made the decision.
    pub approver: String,
    /// Optional comment explaining the decision.
    pub comment: Option<String>,
    /// When the decision was made.
    pub timestamp: DateTime<Utc>,
}

impl ApprovalDecision {
    /// Create a new approval decision.
    pub fn new(
        request_id: Uuid,
        stage: impl Into<String>,
        decision: Decision,
        approver: impl Into<String>,
        comment: Option<String>,
    ) -> Self {
        Self {
            id: Uuid::new_v4(),
            request_id,
            stage: stage.into(),
            decision,
            approver: approver.into(),
            comment,
            timestamp: Utc::now(),
        }
    }
}

/// An approval request for a pattern.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApprovalRequest {
    /// Unique identifier for this request.
    pub id: Uuid,
    /// The pattern ID being reviewed.
    pub pattern_id: Uuid,
    /// Human-readable pattern name.
    pub pattern_name: String,
    /// Name of the workflow being used.
    pub workflow_name: String,
    /// Current status of the request.
    pub status: ApprovalStatus,
    /// Index of the current stage in the workflow.
    pub current_stage_index: usize,
    /// All decisions made on this request.
    pub decisions: Vec<ApprovalDecision>,
    /// When the request was created.
    pub created_at: DateTime<Utc>,
    /// When the request was last updated.
    pub updated_at: DateTime<Utc>,
    /// Who created the request.
    pub created_by: String,
    /// Optional deadline for the current stage.
    pub stage_deadline: Option<DateTime<Utc>>,
    /// Optional summary of evidence for reviewers.
    pub evidence_summary: Option<String>,
}

impl ApprovalRequest {
    /// Create a new approval request.
    pub fn new(
        pattern_id: Uuid,
        pattern_name: impl Into<String>,
        workflow_name: impl Into<String>,
        first_stage: impl Into<String>,
        created_by: impl Into<String>,
    ) -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4(),
            pattern_id,
            pattern_name: pattern_name.into(),
            workflow_name: workflow_name.into(),
            status: ApprovalStatus::Pending { stage: first_stage.into() },
            current_stage_index: 0,
            decisions: Vec::new(),
            created_at: now,
            updated_at: now,
            created_by: created_by.into(),
            stage_deadline: None,
            evidence_summary: None,
        }
    }

    /// Set the stage deadline.
    pub fn with_deadline(mut self, deadline: DateTime<Utc>) -> Self {
        self.stage_deadline = Some(deadline);
        self
    }

    /// Set the evidence summary.
    pub fn with_evidence_summary(mut self, summary: impl Into<String>) -> Self {
        self.evidence_summary = Some(summary.into());
        self
    }

    /// Add a decision to the request.
    pub fn add_decision(&mut self, decision: ApprovalDecision) {
        self.decisions.push(decision);
        self.updated_at = Utc::now();
    }

    /// Advance to the next stage.
    pub fn advance_to_stage(&mut self, stage: impl Into<String>) {
        self.current_stage_index += 1;
        self.status = ApprovalStatus::Pending { stage: stage.into() };
        self.updated_at = Utc::now();
        self.stage_deadline = None; // Will be set by caller if needed
    }

    /// Mark as approved.
    pub fn mark_approved(&mut self) {
        self.status = ApprovalStatus::Approved;
        self.updated_at = Utc::now();
    }

    /// Mark as rejected.
    pub fn mark_rejected(&mut self, stage: impl Into<String>, reason: impl Into<String>) {
        self.status = ApprovalStatus::Rejected { stage: stage.into(), reason: reason.into() };
        self.updated_at = Utc::now();
    }

    /// Mark as escalated.
    pub fn mark_escalated(&mut self, from_stage: impl Into<String>, to_stage: impl Into<String>) {
        let to = to_stage.into();
        self.status =
            ApprovalStatus::Escalated { from_stage: from_stage.into(), to_stage: to.clone() };
        self.updated_at = Utc::now();
        // Also set pending to the escalated stage
        self.status = ApprovalStatus::Pending { stage: to };
    }

    /// Mark as expired.
    pub fn mark_expired(&mut self) {
        self.status = ApprovalStatus::Expired;
        self.updated_at = Utc::now();
    }

    /// Check if the current stage deadline has passed.
    pub fn is_past_deadline(&self) -> bool {
        self.stage_deadline.map(|d| Utc::now() > d).unwrap_or(false)
    }

    /// Get the number of approvals for the current stage.
    pub fn current_stage_approval_count(&self) -> usize {
        if let ApprovalStatus::Pending { stage } = &self.status {
            self.decisions
                .iter()
                .filter(|d| d.stage == *stage && d.decision == Decision::Approved)
                .count()
        } else {
            0
        }
    }
}

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

    #[test]
    fn test_approval_status_pending() {
        let status = ApprovalStatus::Pending { stage: "security_review".to_string() };
        assert!(status.is_pending());
        assert!(!status.is_terminal());
        assert_eq!(status.current_stage(), Some("security_review"));
    }

    #[test]
    fn test_approval_status_terminal() {
        assert!(ApprovalStatus::Approved.is_terminal());
        assert!(ApprovalStatus::Rejected { stage: "test".into(), reason: "reason".into() }
            .is_terminal());
        assert!(ApprovalStatus::Expired.is_terminal());
        assert!(!ApprovalStatus::Pending { stage: "test".into() }.is_terminal());
    }

    #[test]
    fn test_approval_decision_creation() {
        let decision = ApprovalDecision::new(
            Uuid::new_v4(),
            "security_review",
            Decision::Approved,
            "alice",
            Some("LGTM".to_string()),
        );

        assert_eq!(decision.stage, "security_review");
        assert_eq!(decision.decision, Decision::Approved);
        assert_eq!(decision.approver, "alice");
        assert_eq!(decision.comment, Some("LGTM".to_string()));
    }

    #[test]
    fn test_approval_request_lifecycle() {
        let mut request = ApprovalRequest::new(
            Uuid::new_v4(),
            "tls_min_version",
            "standard_review",
            "security_review",
            "system",
        );

        assert!(request.status.is_pending());
        assert_eq!(request.current_stage_index, 0);

        // Add approval decision
        let decision =
            ApprovalDecision::new(request.id, "security_review", Decision::Approved, "alice", None);
        request.add_decision(decision);
        assert_eq!(request.current_stage_approval_count(), 1);

        // Advance to next stage
        request.advance_to_stage("architecture_review");
        assert_eq!(request.current_stage_index, 1);
        assert_eq!(request.status.current_stage(), Some("architecture_review"));

        // Final approval
        request.mark_approved();
        assert!(request.status.is_approved());
        assert!(request.status.is_terminal());
    }

    #[test]
    fn test_approval_request_rejection() {
        let mut request = ApprovalRequest::new(
            Uuid::new_v4(),
            "weak_pattern",
            "standard_review",
            "security_review",
            "system",
        );

        request.mark_rejected("security_review", "Pattern too broad");

        assert!(request.status.is_rejected());
        assert!(request.status.is_terminal());

        if let ApprovalStatus::Rejected { stage, reason } = &request.status {
            assert_eq!(stage, "security_review");
            assert_eq!(reason, "Pattern too broad");
        } else {
            panic!("Expected Rejected status");
        }
    }

    #[test]
    fn test_serialization_roundtrip() {
        let request =
            ApprovalRequest::new(Uuid::new_v4(), "test_pattern", "workflow", "stage1", "user");

        let json = serde_json::to_string(&request).expect("serialize");
        let parsed: ApprovalRequest = serde_json::from_str(&json).expect("deserialize");

        assert_eq!(parsed.id, request.id);
        assert_eq!(parsed.pattern_name, request.pattern_name);
    }
}
