//! L7 Rule types for policy matching

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::L7PolicyId;

/// Unique identifier for an L7 rule
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct L7RuleId(Uuid);

impl L7RuleId {
    /// Create a new random L7RuleId
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    /// Create from existing UUID
    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }

    /// Get the inner UUID
    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }
}

impl Default for L7RuleId {
    fn default() -> Self {
        Self::new()
    }
}

impl std::fmt::Display for L7RuleId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// L7 routing rule (match condition)
///
/// Defines a single match condition for an L7Policy.
/// All rules within a policy must match (AND logic) for the
/// policy action to be executed.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct L7Rule {
    /// Unique identifier
    pub id: L7RuleId,
    /// Parent policy
    pub policy_id: L7PolicyId,

    /// Type of comparison (what to match against)
    pub rule_type: L7RuleType,

    /// Comparison operator
    pub compare_type: L7CompareType,

    /// Value to compare against
    pub value: String,

    /// Key for header/cookie rules (e.g., "X-Custom-Header", "session_id")
    pub key: Option<String>,

    /// Invert the match result (NOT logic)
    pub invert: bool,

    /// Creation timestamp (Unix epoch)
    pub created_at: u64,
    /// Last update timestamp (Unix epoch)
    pub updated_at: u64,
}

/// What to match against
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum L7RuleType {
    /// Match request hostname (Host header or SNI)
    HostName,
    /// Match request path (e.g., /api/v1/users)
    Path,
    /// Match file extension (e.g., .jpg, .css, .js)
    FileType,
    /// Match HTTP header value (requires key field)
    Header,
    /// Match cookie value (requires key field)
    Cookie,
    /// Match SSL SNI hostname (TLS only)
    SslConnHasSni,
}

/// How to compare
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum L7CompareType {
    /// Exact match (case-sensitive)
    EqualTo,
    /// Regex match
    Regex,
    /// String starts with
    StartsWith,
    /// String ends with
    EndsWith,
    /// String contains
    Contains,
}

impl Default for L7CompareType {
    fn default() -> Self {
        Self::EqualTo
    }
}

impl L7Rule {
    /// Create a new L7 rule
    pub fn new(
        policy_id: L7PolicyId,
        rule_type: L7RuleType,
        compare_type: L7CompareType,
        value: impl Into<String>,
    ) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        Self {
            id: L7RuleId::new(),
            policy_id,
            rule_type,
            compare_type,
            value: value.into(),
            key: None,
            invert: false,
            created_at: now,
            updated_at: now,
        }
    }
}
