//! Scope override records with justification.
//!
//! When a team or project overrides an inherited pattern, the override
//! is recorded with reason, evidence, and expiration for audit purposes.

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

use super::ScopeId;

/// Value type for scope overrides.
///
/// Uses explicit tagging to avoid serde(untagged) ambiguity where
/// integers might deserialize as floats or strings as booleans.
///
/// # Serialization Format
///
/// ```json
/// { "type": "text", "value": "1.3" }
/// { "type": "number", "value": 3.14 }
/// { "type": "integer", "value": 42 }
/// { "type": "boolean", "value": true }
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", content = "value")]
pub enum OverrideValue {
    /// A text string value.
    #[serde(rename = "text")]
    Text(String),
    /// A floating-point numeric value.
    #[serde(rename = "number")]
    Number(f64),
    /// An integer value.
    #[serde(rename = "integer")]
    Integer(i64),
    /// A boolean value.
    #[serde(rename = "boolean")]
    Boolean(bool),
}

impl OverrideValue {
    /// Parse a value from a string, inferring the type.
    ///
    /// Attempts to parse in order: boolean, integer, number, then falls back to text.
    pub fn parse(s: &str) -> Self {
        // Try boolean
        if s.eq_ignore_ascii_case("true") {
            return OverrideValue::Boolean(true);
        }
        if s.eq_ignore_ascii_case("false") {
            return OverrideValue::Boolean(false);
        }

        // Try integer
        if let Ok(n) = s.parse::<i64>() {
            return OverrideValue::Integer(n);
        }

        // Try float
        if let Ok(n) = s.parse::<f64>() {
            return OverrideValue::Number(n);
        }

        // Default to text
        OverrideValue::Text(s.to_string())
    }
}

impl From<String> for OverrideValue {
    fn from(s: String) -> Self {
        OverrideValue::Text(s)
    }
}

impl From<&str> for OverrideValue {
    fn from(s: &str) -> Self {
        OverrideValue::Text(s.to_string())
    }
}

impl From<bool> for OverrideValue {
    fn from(b: bool) -> Self {
        OverrideValue::Boolean(b)
    }
}

impl From<i64> for OverrideValue {
    fn from(n: i64) -> Self {
        OverrideValue::Integer(n)
    }
}

impl From<f64> for OverrideValue {
    fn from(n: f64) -> Self {
        OverrideValue::Number(n)
    }
}

impl std::fmt::Display for OverrideValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            OverrideValue::Text(s) => write!(f, "{}", s),
            OverrideValue::Number(n) => write!(f, "{}", n),
            OverrideValue::Integer(n) => write!(f, "{}", n),
            OverrideValue::Boolean(b) => write!(f, "{}", b),
        }
    }
}

/// A recorded scope override with justification.
///
/// Scope overrides provide explicit documentation for when a lower-level
/// scope (team or project) intentionally differs from an inherited pattern.
/// This is required for governance and audit purposes.
///
/// # Example
///
/// A project might override an organization's TLS minimum version:
///
/// ```text
/// Org pattern: tls/min_version = "1.2"
/// Project override: tls/min_version = "1.3"
/// Reason: "Project requires TLS 1.3 for PCI compliance"
/// Evidence: "docs/adr/042.md"
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScopeOverride {
    /// The scope where this override is defined.
    pub scope: ScopeId,

    /// The concept path being overridden (e.g., "tls/min_version").
    pub concept_path: String,

    /// The predicate being overridden (e.g., "version", "enabled").
    pub predicate: String,

    /// The override value.
    pub value: OverrideValue,

    /// Justification for the override (required).
    pub reason: String,

    /// Optional evidence reference (ADR, ticket, spec).
    pub evidence: Option<String>,

    /// When this override was created.
    #[serde(with = "chrono::serde::ts_seconds")]
    pub created_at: DateTime<Utc>,

    /// Who created this override (user or agent).
    pub created_by: Option<String>,

    /// Optional expiration for the override.
    ///
    /// After expiration, the inherited pattern takes effect again.
    #[serde(default)]
    #[serde(with = "option_ts_seconds")]
    pub expires_at: Option<DateTime<Utc>>,
}

impl ScopeOverride {
    /// Create a new scope override.
    pub fn new(
        scope: ScopeId,
        concept_path: impl Into<String>,
        predicate: impl Into<String>,
        value: OverrideValue,
        reason: impl Into<String>,
    ) -> Self {
        Self {
            scope,
            concept_path: concept_path.into(),
            predicate: predicate.into(),
            value,
            reason: reason.into(),
            evidence: None,
            created_at: Utc::now(),
            created_by: None,
            expires_at: None,
        }
    }

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

    /// Set created_by.
    pub fn with_created_by(mut self, created_by: impl Into<String>) -> Self {
        self.created_by = Some(created_by.into());
        self
    }

    /// Set expiration.
    pub fn with_expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
        self.expires_at = Some(expires_at);
        self
    }

    /// Check if this override has expired.
    pub fn is_expired(&self) -> bool {
        self.expires_at.is_some_and(|exp| Utc::now() > exp)
    }

    /// Check if this override is still active (not expired).
    pub fn is_active(&self) -> bool {
        !self.is_expired()
    }

    /// Get days until expiration, or None if no expiration set.
    pub fn days_until_expiration(&self) -> Option<i64> {
        self.expires_at.map(|exp| (exp - Utc::now()).num_days())
    }
}

/// Custom serde for Option<DateTime<Utc>> using ts_seconds.
mod option_ts_seconds {
    use chrono::{DateTime, Utc};
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    pub fn serialize<S>(opt: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match opt {
            Some(dt) => dt.timestamp().serialize(serializer),
            None => serializer.serialize_none(),
        }
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let opt: Option<i64> = Option::deserialize(deserializer)?;
        Ok(opt.and_then(|ts| DateTime::from_timestamp(ts, 0)))
    }
}

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

    #[test]
    fn test_scope_override_creation() {
        let scope = ScopeId::project("api-gateway");
        let override_record = ScopeOverride::new(
            scope.clone(),
            "tls/min_version",
            "version",
            OverrideValue::Text("1.3".to_string()),
            "Project requires TLS 1.3",
        );

        assert_eq!(override_record.scope.level, ScopeLevel::Project);
        assert_eq!(override_record.concept_path, "tls/min_version");
        assert_eq!(override_record.reason, "Project requires TLS 1.3");
        assert!(override_record.is_active());
    }

    #[test]
    fn test_scope_override_with_evidence() {
        let scope = ScopeId::team("platform");
        let override_record = ScopeOverride::new(
            scope,
            "db/pool_size",
            "size",
            OverrideValue::Integer(50),
            "Team needs larger pool",
        )
        .with_evidence("JIRA-1234")
        .with_created_by("jane@example.com");

        assert_eq!(override_record.evidence, Some("JIRA-1234".to_string()));
        assert_eq!(override_record.created_by, Some("jane@example.com".to_string()));
    }

    #[test]
    fn test_scope_override_expiration() {
        use chrono::Duration;

        let scope = ScopeId::project("test");

        // Create an already expired override
        let expired = ScopeOverride::new(
            scope.clone(),
            "test/path",
            "predicate",
            OverrideValue::Boolean(true),
            "test",
        )
        .with_expires_at(Utc::now() - Duration::days(1));

        assert!(expired.is_expired());
        assert!(!expired.is_active());

        // Create a future expiration
        let future = ScopeOverride::new(
            scope,
            "test/path",
            "predicate",
            OverrideValue::Boolean(true),
            "test",
        )
        .with_expires_at(Utc::now() + Duration::days(30));

        assert!(!future.is_expired());
        assert!(future.is_active());
        assert!(future.days_until_expiration().unwrap_or(0) >= 29);
    }

    #[test]
    fn test_scope_override_serde() {
        let scope = ScopeId::organization("acme");
        let override_record = ScopeOverride::new(
            scope,
            "security/mfa",
            "required",
            OverrideValue::Boolean(true),
            "Org policy requires MFA",
        );

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

        assert_eq!(parsed.concept_path, "security/mfa");
        assert_eq!(parsed.reason, "Org policy requires MFA");
    }

    #[test]
    fn test_override_value_parse() {
        // Boolean parsing
        assert_eq!(OverrideValue::parse("true"), OverrideValue::Boolean(true));
        assert_eq!(OverrideValue::parse("TRUE"), OverrideValue::Boolean(true));
        assert_eq!(OverrideValue::parse("false"), OverrideValue::Boolean(false));

        // Integer parsing
        assert_eq!(OverrideValue::parse("42"), OverrideValue::Integer(42));
        assert_eq!(OverrideValue::parse("-10"), OverrideValue::Integer(-10));

        // Float parsing
        assert_eq!(OverrideValue::parse("7.25"), OverrideValue::Number(7.25));

        // Text fallback
        assert_eq!(OverrideValue::parse("hello"), OverrideValue::Text("hello".to_string()));
        assert_eq!(OverrideValue::parse("1.3"), OverrideValue::Number(1.3));
    }

    #[test]
    fn test_override_value_serde_explicit_tag() {
        // Test that explicit tagging works correctly
        let text = OverrideValue::Text("hello".to_string());
        let json = serde_json::to_string(&text).expect("serialize");
        assert!(json.contains("\"type\":\"text\""));
        assert!(json.contains("\"value\":\"hello\""));

        let int = OverrideValue::Integer(42);
        let json = serde_json::to_string(&int).expect("serialize");
        assert!(json.contains("\"type\":\"integer\""));
        assert!(json.contains("\"value\":42"));

        // Roundtrip
        let parsed: OverrideValue = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(parsed, OverrideValue::Integer(42));
    }
}
