use serde::{Deserialize, Serialize};

/// An unsigned ops proposal — arrives on ops.pending.<workspace>.<op_type>
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PendingOp {
    /// Operation type: deploy | scale | restart | secret_update | drain
    pub op_type: String,
    /// Deployment target name (e.g., "staging-vapora", "libre-wuji-api")
    pub target: String,
    /// Requesting principal (e.g., "woodpecker-job-42", "manual-jpl")
    pub sub: String,
    /// Optimistic concurrency token — workspace state version the emitter read
    pub expected_state_version: String,
    /// Image reference (for deploy ops; empty for others)
    #[serde(default)]
    pub image: String,
    /// Additional op-specific parameters
    #[serde(default)]
    pub params: serde_json::Value,
}

/// One scope tuple embedded in the JWT claims
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScopeEntry {
    pub op_type: String,
    pub target_pattern: String,
}

/// JWT claims payload for a signed ops command (ops.cmd.*)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpsClaims {
    /// Signer identity: keeper-vm-primary | operator-<id> | gh-actions-<id>
    pub iss: String,
    /// Requesting principal (copied from PendingOp.sub)
    pub sub: String,
    /// Target workspace name
    pub aud: SingleOrVec,
    /// Scope tuples authorised for this signer
    pub scopes: Vec<ScopeEntry>,
    /// Per-issuer monotonic counter — anti-replay
    pub seq: u64,
    /// UUIDv4 idempotency key
    pub jti: String,
    /// Optimistic concurrency token (copied from PendingOp)
    pub expected_state_version: String,
    /// Unix timestamp: token expiry
    pub exp: u64,
    /// Unix timestamp: token not-valid-before
    pub nbf: u64,
    /// The pending op being authorised
    pub op: PendingOp,
}

/// Allows `aud` to be either a single string or an array — jsonwebtoken normalises this
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SingleOrVec {
    Single(String),
    Many(Vec<String>),
}

impl SingleOrVec {
    pub fn single(s: impl Into<String>) -> Self {
        Self::Single(s.into())
    }
}

/// An audit event published to ops.audit.<workspace>
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEvent {
    pub jti: String,
    pub workspace: String,
    pub op_type: String,
    pub target: String,
    pub signer_iss: String,
    /// Unix timestamp (seconds)
    pub signed_at: i64,
    /// "auto_sign" | "require_manual" | "hold_pending"
    pub decision: String,
}
