//! Protocol-domain models used by the Rust TSO service implementation.
//! These structs mirror the wire-level fields in `tsopb`/`pdpb` needed by
//! the service logic, without binding to a specific gRPC runtime.

/// Number of bits reserved for logical part in composed TSO.
pub const PHYSICAL_SHIFT_BITS: u32 = 18;
/// Maximum logical value before overflow handling is required.
pub const MAX_LOGICAL: i64 = 1 << PHYSICAL_SHIFT_BITS;

/// Timestamp returned by TSO allocation APIs.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Timestamp {
    /// Physical timestamp component in milliseconds.
    pub physical: i64,
    /// Logical counter component within one physical millisecond.
    pub logical: i64,
    /// Suffix bits used by compatibility layouts that split logical space.
    pub suffix_bits: u32,
}

impl Timestamp {
    /// Returns true when both physical and logical parts are zero.
    #[must_use]
    pub fn is_zero(self) -> bool {
        self.physical == 0 && self.logical == 0
    }

    /// Composes a 64-bit TSO value from physical/logical components.
    #[must_use]
    pub fn compose(self) -> u64 {
        ((self.physical as u64) << PHYSICAL_SHIFT_BITS) | ((self.logical as u64) & 0x3f_fff)
    }

    /// Parses a 64-bit TSO value into timestamp components.
    #[must_use]
    pub fn parse(ts: u64) -> Self {
        Self {
            physical: (ts >> PHYSICAL_SHIFT_BITS) as i64,
            logical: (ts & 0x3f_fff) as i64,
            suffix_bits: 0,
        }
    }
}

/// `tsopb.ErrorType` compatibility enum.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorType {
    /// No handled error is present.
    Ok,
    /// Generic handled error classification.
    Unknown,
    /// Cluster/service has not been bootstrapped.
    NotBootstrapped,
    /// Cluster/service is already bootstrapped.
    AlreadyBootstrapped,
    /// Request contains an invalid value.
    InvalidValue,
    /// Request cluster id does not match the serving cluster.
    ClusterMismatched,
}

/// Error payload used in handled response envelopes.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error {
    /// Error type code aligned with compatibility enums.
    pub typ: ErrorType,
    /// Human-readable error message.
    pub message: String,
}

impl Error {
    /// Creates a handled error payload with type and message.
    #[must_use]
    pub fn new(typ: ErrorType, message: impl Into<String>) -> Self {
        Self {
            typ,
            message: message.into(),
        }
    }
}

/// Request header used by TSO APIs.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RequestHeader {
    /// Target cluster identifier.
    pub cluster_id: u64,
    /// Caller/node sender identifier.
    pub sender_id: u64,
    /// Keyspace identifier.
    pub keyspace_id: u32,
    /// Keyspace-group identifier.
    pub keyspace_group_id: u32,
}

/// Response header used by TSO APIs.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ResponseHeader {
    /// Serving cluster identifier.
    pub cluster_id: u64,
    /// Optional handled error payload.
    pub error: Option<Error>,
    /// Echoed or resolved keyspace identifier.
    pub keyspace_id: u32,
    /// Echoed or resolved keyspace-group identifier.
    pub keyspace_group_id: u32,
}

impl ResponseHeader {
    /// Builds a successful response header with `error = None`.
    #[must_use]
    pub fn ok(cluster_id: u64, keyspace_group_id: u32) -> Self {
        Self {
            cluster_id,
            error: None,
            keyspace_id: 0,
            keyspace_group_id,
        }
    }

    /// Builds a handled-error response header.
    #[must_use]
    pub fn with_error(cluster_id: u64, keyspace_group_id: u32, error: Error) -> Self {
        Self {
            cluster_id,
            error: Some(error),
            keyspace_id: 0,
            keyspace_group_id,
        }
    }
}

/// Streaming TSO request.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TsoRequest {
    /// Request header.
    pub header: RequestHeader,
    /// Number of timestamps requested in this batch.
    pub count: u32,
    /// Datacenter-location routing hint.
    pub dc_location: String,
}

/// Streaming TSO response.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TsoResponse {
    /// Response header.
    pub header: ResponseHeader,
    /// Number of timestamps allocated for this response.
    pub count: u32,
    /// Highest timestamp allocated for this response window.
    pub timestamp: Timestamp,
}

/// Keyspace-group member metadata.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct KeyspaceGroupMember {
    /// Member advertise address or identity string.
    pub address: String,
    /// Whether this member is currently primary for the group.
    pub is_primary: bool,
}

/// Split-state metadata.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SplitState {
    /// Source group id from which this group is splitting.
    pub split_source: u32,
}

/// Merge-state metadata.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct MergeState {
    /// Group ids being merged into this target group.
    pub merge_list: Vec<u32>,
}

/// Keyspace-group metadata payload.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct KeyspaceGroup {
    /// Keyspace-group identifier.
    pub id: u32,
    /// User classification/kind metadata.
    pub user_kind: String,
    /// Optional split-state metadata.
    pub split_state: Option<SplitState>,
    /// Optional merge-state metadata.
    pub merge_state: Option<MergeState>,
    /// Member topology for this group.
    pub members: Vec<KeyspaceGroupMember>,
    /// Keyspace ids assigned to this group.
    pub keyspaces: Vec<u32>,
}

/// Unary request to resolve group by keyspace id.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct FindGroupByKeyspaceIdRequest {
    /// Request header.
    pub header: RequestHeader,
    /// Target keyspace id to resolve.
    pub keyspace_id: u32,
    /// Caller-observed membership revision for cache validation.
    pub mod_revision: u64,
}

/// Unary response for group-by-keyspace lookup.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct FindGroupByKeyspaceIdResponse {
    /// Response header.
    pub header: ResponseHeader,
    /// Resolved keyspace-group metadata when found.
    pub keyspace_group: Option<KeyspaceGroup>,
    /// Server-observed membership revision.
    pub mod_revision: u64,
}

/// Unary request to query minimum timestamp.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct GetMinTsRequest {
    /// Request header.
    pub header: RequestHeader,
    /// Datacenter-location routing hint.
    pub dc_location: String,
}

/// Unary response to minimum timestamp query.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct GetMinTsResponse {
    /// Response header.
    pub header: ResponseHeader,
    /// Minimum timestamp across evaluated groups.
    pub timestamp: Timestamp,
    /// Count of groups currently serving.
    pub keyspace_groups_serving: u32,
    /// Total count of groups considered in evaluation.
    pub keyspace_groups_total: u32,
}

#[cfg(test)]
mod tests {
    use super::{MAX_LOGICAL, Timestamp};

    #[test]
    fn timestamp_compose_round_trip() {
        let ts = Timestamp {
            physical: 1_739_841_234_567,
            logical: 123,
            suffix_bits: 0,
        };
        let parsed = Timestamp::parse(ts.compose());
        assert_eq!(parsed.physical, ts.physical);
        assert_eq!(parsed.logical, ts.logical);
    }

    #[test]
    fn max_logical_constant_matches_contract() {
        assert_eq!(MAX_LOGICAL, 1 << 18);
    }
}
