//! Grant-status value envelope for confidential-grant leaves (PDS §5.2).
//!
//! Confidential-grant leaves store the authorization status for a
//! `(provider, policy_client, domain_id)` triple as a versioned 2-byte payload:
//! `[version, status]`. Operators serving Confidential-namespace
//! `newt_signedRead` requests MUST decode the leaf value and verify
//! `status == GrantStatus::Accepted` before returning data.
//!
//! See PDS §5.2 for the strict status state machine
//! (`None → Proposed → Accepted → Revoked`) and §9.2 Layer 2 for the operator
//! authorization predicate that consumes this envelope.

use thiserror::Error;

/// Lifecycle state of a confidential-grant authorization.
///
/// State machine (one-way arrows; `Revoked` is terminal):
/// `None → Proposed → Accepted → Revoked`.
///
/// Only `Accepted` authorizes operators to serve confidential data on the
/// associated `(provider, policy_client, domain_id)` triple.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum GrantStatus {
    /// No grant exists. Wire form `0x00`. Operators MUST refuse reads.
    None = 0,
    /// Provider has proposed a grant; awaiting policy-client owner acceptance.
    /// Wire form `0x01`. Operators MUST refuse reads.
    Proposed = 1,
    /// Both parties have signed off; the only state that authorizes serving data.
    /// Wire form `0x02`.
    Accepted = 2,
    /// Either party has revoked the grant. Wire form `0x03`. Operators MUST
    /// refuse reads. Terminal — never returns to `Accepted`.
    Revoked = 3,
}

impl GrantStatus {
    /// The wire-format byte for this status.
    pub fn byte(self) -> u8 {
        self as u8
    }

    /// Decode a wire-format byte back into a [`GrantStatus`], or `None` for
    /// any byte value not in `0..=3`.
    pub fn from_byte(b: u8) -> Option<Self> {
        match b {
            0 => Some(Self::None),
            1 => Some(Self::Proposed),
            2 => Some(Self::Accepted),
            3 => Some(Self::Revoked),
            _ => None,
        }
    }
}

/// Errors decoding a [`GrantValue`] from its on-the-wire byte form.
#[derive(Debug, Error, PartialEq, Eq)]
pub enum GrantDecodeError {
    /// Buffer is shorter than the 2-byte minimum (`[version, status]`).
    #[error("grant value too short: {got} bytes (need >= 2)")]
    TooShort {
        /// Byte length that was supplied.
        got: usize,
    },
    /// Version byte is not a recognized release.
    #[error("unsupported grant value version: {got}")]
    BadVersion {
        /// Version byte that was supplied.
        got: u8,
    },
    /// Status byte is not in the recognized state machine.
    #[error("unknown grant status byte: 0x{got:02x}")]
    BadStatus {
        /// Status byte that was supplied.
        got: u8,
    },
}

/// On-chain → JMT-leaf encoding of a confidential-grant authorization.
///
/// Wire form for `VERSION_V1`: `[version: u8, status: u8]` (2 bytes).
/// Future versions may extend the trailer; decoders MUST reject unknown
/// versions rather than silently treating them as v1.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GrantValue {
    /// Encoding version. Currently always [`Self::VERSION_V1`].
    pub version: u8,
    /// Authorization lifecycle state.
    pub status: GrantStatus,
}

impl GrantValue {
    /// Initial release; defines the 2-byte `[version, status]` layout.
    pub const VERSION_V1: u8 = 1;

    /// Construct a v1 envelope at the given status.
    pub fn new_v1(status: GrantStatus) -> Self {
        Self {
            version: Self::VERSION_V1,
            status,
        }
    }

    /// Encode to the canonical 2-byte wire form `[version, status]`.
    pub fn encode(&self) -> Vec<u8> {
        vec![self.version, self.status.byte()]
    }

    /// Decode from the canonical wire form. Rejects unknown versions and
    /// unknown status bytes with typed errors.
    pub fn decode(bytes: &[u8]) -> Result<Self, GrantDecodeError> {
        if bytes.len() < 2 {
            return Err(GrantDecodeError::TooShort { got: bytes.len() });
        }
        let version = bytes[0];
        if version != Self::VERSION_V1 {
            return Err(GrantDecodeError::BadVersion { got: version });
        }
        let status = GrantStatus::from_byte(bytes[1]).ok_or(GrantDecodeError::BadStatus { got: bytes[1] })?;
        Ok(Self { version, status })
    }
}

/// Convenience: decode just the [`GrantStatus`] from a leaf value, returning
/// `None` on any decode error. Used by operator authorization predicates that
/// only need the lifecycle state and treat malformed envelopes as "not authorized."
pub fn extract_grant_status(bytes: &[u8]) -> Option<GrantStatus> {
    GrantValue::decode(bytes).ok().map(|v| v.status)
}

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

    #[test]
    fn round_trip_all_statuses() {
        for status in [
            GrantStatus::None,
            GrantStatus::Proposed,
            GrantStatus::Accepted,
            GrantStatus::Revoked,
        ] {
            let envelope = GrantValue::new_v1(status);
            let bytes = envelope.encode();
            let decoded = GrantValue::decode(&bytes).expect("canonical encoding must decode");
            assert_eq!(decoded, envelope);
            assert_eq!(decoded.status, status);
            assert_eq!(decoded.version, GrantValue::VERSION_V1);
        }
    }

    #[test]
    fn decode_rejects_too_short() {
        assert_eq!(GrantValue::decode(&[]), Err(GrantDecodeError::TooShort { got: 0 }));
        assert_eq!(
            GrantValue::decode(&[GrantValue::VERSION_V1]),
            Err(GrantDecodeError::TooShort { got: 1 })
        );
    }

    #[test]
    fn decode_rejects_unknown_version() {
        let bytes = [0u8, GrantStatus::Accepted.byte()];
        assert_eq!(GrantValue::decode(&bytes), Err(GrantDecodeError::BadVersion { got: 0 }));

        let bytes = [255u8, GrantStatus::Accepted.byte()];
        assert_eq!(
            GrantValue::decode(&bytes),
            Err(GrantDecodeError::BadVersion { got: 255 })
        );
    }

    #[test]
    fn decode_rejects_unknown_status() {
        let bytes = [GrantValue::VERSION_V1, 0x04];
        assert_eq!(
            GrantValue::decode(&bytes),
            Err(GrantDecodeError::BadStatus { got: 0x04 })
        );

        let bytes = [GrantValue::VERSION_V1, 0xff];
        assert_eq!(
            GrantValue::decode(&bytes),
            Err(GrantDecodeError::BadStatus { got: 0xff })
        );
    }

    #[test]
    fn extract_grant_status_returns_none_on_error() {
        assert_eq!(extract_grant_status(&[]), None);
        assert_eq!(extract_grant_status(&[0u8, 2u8]), None); // bad version
        assert_eq!(extract_grant_status(&[1u8, 0x99u8]), None); // bad status
    }

    #[test]
    fn extract_grant_status_returns_status_on_success() {
        let bytes = GrantValue::new_v1(GrantStatus::Accepted).encode();
        assert_eq!(extract_grant_status(&bytes), Some(GrantStatus::Accepted));

        let bytes = GrantValue::new_v1(GrantStatus::Proposed).encode();
        assert_eq!(extract_grant_status(&bytes), Some(GrantStatus::Proposed));
    }

    #[test]
    fn from_byte_round_trip() {
        for s in [
            GrantStatus::None,
            GrantStatus::Proposed,
            GrantStatus::Accepted,
            GrantStatus::Revoked,
        ] {
            assert_eq!(GrantStatus::from_byte(s.byte()), Some(s));
        }
        assert_eq!(GrantStatus::from_byte(0x04), None);
        assert_eq!(GrantStatus::from_byte(0xff), None);
    }

    #[test]
    fn encoding_canonical_layout() {
        // Pin the wire layout against drift — readers that hand-decode without
        // calling decode() rely on this exact byte ordering.
        let bytes = GrantValue::new_v1(GrantStatus::None).encode();
        assert_eq!(bytes, vec![1u8, 0u8]);

        let bytes = GrantValue::new_v1(GrantStatus::Proposed).encode();
        assert_eq!(bytes, vec![1u8, 1u8]);

        let bytes = GrantValue::new_v1(GrantStatus::Accepted).encode();
        assert_eq!(bytes, vec![1u8, 2u8]);

        let bytes = GrantValue::new_v1(GrantStatus::Revoked).encode();
        assert_eq!(bytes, vec![1u8, 3u8]);
    }
}
