// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use byteorder::{ByteOrder, LittleEndian};
use std::fmt;

use crate::{Result, ResyncStateError};

pub use crate::filemeta::{MrfOpKind, MrfReplicateEntry};

pub const MRF_META_FORMAT: u16 = 1;
pub const MRF_META_VERSION: u16 = 1;

const MRF_ENVELOPE_MAGIC: [u8; 4] = *b"MRFE";
const MRF_ENVELOPE_HEADER_LEN: usize = 24;
pub const MRF_ENVELOPE_FORMAT: u16 = 1;
pub const MRF_ENVELOPE_VERSION: u16 = 1;

const CAPABILITY_OPERATION_KIND: u64 = 1 << 0;
const CAPABILITY_TARGET_ARNS: u64 = 1 << 1;
const CAPABILITY_FORCE_DELETE: u64 = 1 << 2;
const CAPABILITY_DELETE_MARKER_MTIME: u64 = 1 << 3;
// Per-ARN target-assigned delete-marker version ids on purge intents (backlog#2290).
const CAPABILITY_TARGET_DELETE_MARKER_VERSION_IDS: u64 = 1 << 4;
const MRF_KNOWN_CAPABILITIES: u64 = CAPABILITY_OPERATION_KIND
    | CAPABILITY_TARGET_ARNS
    | CAPABILITY_FORCE_DELETE
    | CAPABILITY_DELETE_MARKER_MTIME
    | CAPABILITY_TARGET_DELETE_MARKER_VERSION_IDS;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MrfCapability {
    OperationKind,
    TargetArns,
    ForceDelete,
    DeleteMarkerMtime,
    TargetDeleteMarkerVersionIds,
}

impl MrfCapability {
    const fn bit(self) -> u64 {
        match self {
            Self::OperationKind => CAPABILITY_OPERATION_KIND,
            Self::TargetArns => CAPABILITY_TARGET_ARNS,
            Self::ForceDelete => CAPABILITY_FORCE_DELETE,
            Self::DeleteMarkerMtime => CAPABILITY_DELETE_MARKER_MTIME,
            Self::TargetDeleteMarkerVersionIds => CAPABILITY_TARGET_DELETE_MARKER_VERSION_IDS,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct MrfCapabilities(u64);

impl MrfCapabilities {
    pub const fn current() -> Self {
        Self(MRF_KNOWN_CAPABILITIES)
    }

    pub const fn empty() -> Self {
        Self(0)
    }

    pub const fn with(capability: MrfCapability) -> Self {
        Self(capability.bit())
    }

    pub const fn bits(self) -> u64 {
        self.0
    }

    pub const fn contains(self, capability: MrfCapability) -> bool {
        self.0 & capability.bit() != 0
    }

    pub const fn supports(self, required: Self) -> bool {
        self.0 & required.0 == required.0
    }

    pub fn from_bits(bits: u64) -> std::result::Result<Self, MrfEnvelopeError> {
        let unknown = bits & !MRF_KNOWN_CAPABILITIES;
        if unknown != 0 {
            return Err(MrfEnvelopeError::UnknownCapabilities { bits: unknown });
        }
        Ok(Self(bits))
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MrfProtocolCapabilities {
    version: u16,
    min_reader_version: u16,
    capabilities: MrfCapabilities,
}

impl MrfProtocolCapabilities {
    pub const fn new(version: u16, min_reader_version: u16, capabilities: MrfCapabilities) -> Self {
        Self {
            version,
            min_reader_version,
            capabilities,
        }
    }

    pub const fn current() -> Self {
        Self {
            version: MRF_ENVELOPE_VERSION,
            min_reader_version: MRF_ENVELOPE_VERSION,
            capabilities: MrfCapabilities::current(),
        }
    }

    pub const fn version(self) -> u16 {
        self.version
    }

    pub const fn min_reader_version(self) -> u16 {
        self.min_reader_version
    }

    pub const fn capabilities(self) -> MrfCapabilities {
        self.capabilities
    }

    pub fn negotiate(self, peer: Self) -> std::result::Result<Self, MrfEnvelopeError> {
        MrfCapabilities::from_bits(self.capabilities.bits())?;
        MrfCapabilities::from_bits(peer.capabilities.bits())?;
        if self.min_reader_version > self.version {
            return Err(MrfEnvelopeError::InvalidVersionRange {
                version: self.version,
                min_reader_version: self.min_reader_version,
            });
        }
        if peer.min_reader_version > peer.version {
            return Err(MrfEnvelopeError::InvalidVersionRange {
                version: peer.version,
                min_reader_version: peer.min_reader_version,
            });
        }
        if self.version < MRF_ENVELOPE_VERSION {
            return Err(MrfEnvelopeError::UnsupportedVersion { version: self.version });
        }
        if peer.version < MRF_ENVELOPE_VERSION {
            return Err(MrfEnvelopeError::UnsupportedVersion { version: peer.version });
        }
        if peer.min_reader_version > self.version {
            return Err(MrfEnvelopeError::RollbackFenced {
                min_reader_version: peer.min_reader_version,
                supported_version: self.version,
            });
        }
        if self.min_reader_version > peer.version {
            return Err(MrfEnvelopeError::RollbackFenced {
                min_reader_version: self.min_reader_version,
                supported_version: peer.version,
            });
        }
        Ok(Self {
            version: self.version.min(peer.version),
            min_reader_version: self.min_reader_version.max(peer.min_reader_version),
            capabilities: MrfCapabilities(self.capabilities.bits() & peer.capabilities.bits()),
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MrfEnvelope {
    protocol: MrfProtocolCapabilities,
    payload: Vec<u8>,
}

impl MrfEnvelope {
    pub fn new(protocol: MrfProtocolCapabilities, payload: Vec<u8>) -> std::result::Result<Self, MrfEnvelopeError> {
        if protocol.version != MRF_ENVELOPE_VERSION {
            return Err(MrfEnvelopeError::UnsupportedVersion {
                version: protocol.version,
            });
        }
        if protocol.min_reader_version > protocol.version {
            return Err(MrfEnvelopeError::InvalidVersionRange {
                version: protocol.version,
                min_reader_version: protocol.min_reader_version,
            });
        }
        MrfCapabilities::from_bits(protocol.capabilities.bits())?;
        Ok(Self { protocol, payload })
    }

    pub const fn protocol(&self) -> MrfProtocolCapabilities {
        self.protocol
    }

    pub fn payload(&self) -> &[u8] {
        &self.payload
    }

    pub fn encode(&self) -> std::result::Result<Vec<u8>, MrfEnvelopeError> {
        let payload_len: u32 = self.payload.len().try_into().map_err(|_| MrfEnvelopeError::PayloadTooLarge)?;
        let mut data = Vec::with_capacity(MRF_ENVELOPE_HEADER_LEN + self.payload.len());
        data.extend_from_slice(&MRF_ENVELOPE_MAGIC);
        data.extend_from_slice(&MRF_ENVELOPE_FORMAT.to_le_bytes());
        data.extend_from_slice(&self.protocol.version.to_le_bytes());
        data.extend_from_slice(&self.protocol.min_reader_version.to_le_bytes());
        data.extend_from_slice(&0u16.to_le_bytes());
        data.extend_from_slice(&self.protocol.capabilities.bits().to_le_bytes());
        data.extend_from_slice(&payload_len.to_le_bytes());
        data.extend_from_slice(&self.payload);
        Ok(data)
    }

    pub fn decode(data: &[u8], supported: MrfProtocolCapabilities) -> std::result::Result<Self, MrfEnvelopeError> {
        if data.len() < MRF_ENVELOPE_HEADER_LEN {
            return Err(MrfEnvelopeError::Truncated);
        }
        if data[..4] != MRF_ENVELOPE_MAGIC {
            return Err(MrfEnvelopeError::InvalidMagic);
        }
        let format = LittleEndian::read_u16(&data[4..6]);
        if format != MRF_ENVELOPE_FORMAT {
            return Err(MrfEnvelopeError::UnsupportedFormat { format });
        }
        let version = LittleEndian::read_u16(&data[6..8]);
        if version < MRF_ENVELOPE_VERSION {
            return Err(MrfEnvelopeError::UnsupportedVersion { version });
        }
        let min_reader_version = LittleEndian::read_u16(&data[8..10]);
        if min_reader_version > supported.version {
            return Err(MrfEnvelopeError::RollbackFenced {
                min_reader_version,
                supported_version: supported.version,
            });
        }
        if min_reader_version > version {
            return Err(MrfEnvelopeError::InvalidVersionRange {
                version,
                min_reader_version,
            });
        }
        let reserved = LittleEndian::read_u16(&data[10..12]);
        if reserved != 0 {
            return Err(MrfEnvelopeError::ReservedHeaderBits { bits: reserved });
        }
        let capabilities = MrfCapabilities::from_bits(LittleEndian::read_u64(&data[12..20]))?;
        if !supported.capabilities.supports(capabilities) {
            return Err(MrfEnvelopeError::MissingCapabilities {
                required: capabilities.bits(),
                available: supported.capabilities.bits(),
            });
        }
        let payload_len = LittleEndian::read_u32(&data[20..24]);
        let actual_len = data.len() - MRF_ENVELOPE_HEADER_LEN;
        if usize::try_from(payload_len).map_err(|_| MrfEnvelopeError::PayloadTooLarge)? != actual_len {
            return Err(MrfEnvelopeError::PayloadLengthMismatch {
                declared: payload_len,
                actual: actual_len,
            });
        }
        Ok(Self {
            protocol: MrfProtocolCapabilities {
                version,
                min_reader_version,
                capabilities,
            },
            payload: data[MRF_ENVELOPE_HEADER_LEN..].to_vec(),
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MrfEnvelopeError {
    Truncated,
    InvalidMagic,
    UnsupportedFormat {
        format: u16,
    },
    UnsupportedVersion {
        version: u16,
    },
    InvalidVersionRange {
        version: u16,
        min_reader_version: u16,
    },
    RollbackFenced {
        min_reader_version: u16,
        supported_version: u16,
    },
    ReservedHeaderBits {
        bits: u16,
    },
    UnknownCapabilities {
        bits: u64,
    },
    MissingCapabilities {
        required: u64,
        available: u64,
    },
    PayloadLengthMismatch {
        declared: u32,
        actual: usize,
    },
    PayloadTooLarge,
}

impl fmt::Display for MrfEnvelopeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Truncated => write!(f, "truncated MRF envelope"),
            Self::InvalidMagic => write!(f, "invalid MRF envelope magic"),
            Self::UnsupportedFormat { format } => write!(f, "unsupported MRF envelope format {format}"),
            Self::UnsupportedVersion { version } => write!(f, "unsupported MRF envelope version {version}"),
            Self::InvalidVersionRange {
                version,
                min_reader_version,
            } => {
                write!(f, "invalid MRF version range: version {version}, minimum reader {min_reader_version}")
            }
            Self::RollbackFenced {
                min_reader_version,
                supported_version,
            } => write!(
                f,
                "MRF rollback fenced: reader version {supported_version} is below required {min_reader_version}"
            ),
            Self::ReservedHeaderBits { bits } => write!(f, "reserved MRF envelope header bits are set: 0x{bits:04x}"),
            Self::UnknownCapabilities { bits } => write!(f, "unknown MRF capability bits 0x{bits:016x}"),
            Self::MissingCapabilities { required, available } => {
                write!(f, "MRF capabilities 0x{available:016x} do not satisfy required 0x{required:016x}")
            }
            Self::PayloadLengthMismatch { declared, actual } => {
                write!(f, "MRF payload length is {actual}, expected {declared}")
            }
            Self::PayloadTooLarge => write!(f, "MRF payload exceeds the envelope length limit"),
        }
    }
}

impl std::error::Error for MrfEnvelopeError {}

pub const MRF_V2_NAMESPACE: &str = "config/replication-v2";
pub const MRF_V2_FILE: &str = "config/replication-v2/mrf.bin";
pub const MRF_V2_FORMAT: u16 = 2;
pub const MRF_V2_VERSION: u16 = 2;

const MRF_V2_MAGIC: [u8; 4] = *b"MRF2";
const MRF_V2_HEADER_LEN: usize = 24;
const MRF_V2_KNOWN_CAPABILITIES: u64 = 0b1111;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MrfV2Error {
    InvalidMagic,
    Truncated,
    UnsupportedNamespace { namespace: String },
    UnsupportedFormat { format: u16 },
    UnsupportedVersion { version: u16 },
    InvalidVersionRange { version: u16, min_reader_version: u16 },
    RollbackFenced { min_reader_version: u16, reader_version: u16 },
    ReservedHeaderBits { bits: u16 },
    UnknownCapabilities { bits: u64 },
    MissingCapabilities { required: u64, available: u64 },
    PayloadLengthMismatch { declared: u32, actual: usize },
    WriterEnabled,
}

impl std::fmt::Display for MrfV2Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidMagic => write!(f, "invalid MRF v2 magic"),
            Self::Truncated => write!(f, "truncated MRF v2 envelope"),
            Self::UnsupportedNamespace { namespace } => write!(f, "unsupported MRF v2 namespace {namespace}"),
            Self::UnsupportedFormat { format } => write!(f, "unsupported MRF v2 format {format}"),
            Self::UnsupportedVersion { version } => write!(f, "unsupported MRF v2 version {version}"),
            Self::InvalidVersionRange {
                version,
                min_reader_version,
            } => {
                write!(f, "invalid MRF v2 version range: version {version}, minimum reader {min_reader_version}")
            }
            Self::RollbackFenced {
                min_reader_version,
                reader_version,
            } => write!(
                f,
                "MRF v2 rollback fenced: reader version {reader_version} is below required {min_reader_version}"
            ),
            Self::ReservedHeaderBits { bits } => write!(f, "reserved MRF v2 header bits are set: 0x{bits:04x}"),
            Self::UnknownCapabilities { bits } => write!(f, "unknown MRF v2 capability bits 0x{bits:016x}"),
            Self::MissingCapabilities { required, available } => {
                write!(f, "MRF v2 capabilities 0x{available:016x} do not satisfy required 0x{required:016x}")
            }
            Self::PayloadLengthMismatch { declared, actual } => {
                write!(f, "MRF v2 payload is {actual} bytes, expected {declared}")
            }
            Self::WriterEnabled => write!(f, "MRF v2 writer must remain dormant"),
        }
    }
}

impl std::error::Error for MrfV2Error {}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct MrfV2Capabilities(u64);

impl MrfV2Capabilities {
    pub const fn current() -> Self {
        Self(MRF_V2_KNOWN_CAPABILITIES)
    }

    pub const fn empty() -> Self {
        Self(0)
    }

    pub const fn bits(self) -> u64 {
        self.0
    }

    pub fn from_bits(bits: u64) -> std::result::Result<Self, MrfV2Error> {
        let unknown = bits & !MRF_V2_KNOWN_CAPABILITIES;
        if unknown != 0 {
            return Err(MrfV2Error::UnknownCapabilities { bits: unknown });
        }
        Ok(Self(bits))
    }

    pub const fn supports(self, required: Self) -> bool {
        self.0 & required.0 == required.0
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MrfV2Readiness {
    reader_version: u16,
    capabilities: MrfV2Capabilities,
    writer_enabled: bool,
}

impl MrfV2Readiness {
    pub const fn dormant() -> Self {
        Self {
            reader_version: MRF_V2_VERSION,
            capabilities: MrfV2Capabilities::current(),
            writer_enabled: false,
        }
    }

    pub const fn new(reader_version: u16, capabilities: MrfV2Capabilities, writer_enabled: bool) -> Self {
        Self {
            reader_version,
            capabilities,
            writer_enabled,
        }
    }

    pub const fn writer_enabled(self) -> bool {
        self.writer_enabled
    }

    pub fn reader(self) -> std::result::Result<MrfV2Reader, MrfV2Error> {
        if self.writer_enabled {
            return Err(MrfV2Error::WriterEnabled);
        }
        if self.reader_version != MRF_V2_VERSION {
            return Err(MrfV2Error::UnsupportedVersion {
                version: self.reader_version,
            });
        }
        MrfV2Capabilities::from_bits(self.capabilities.bits())?;
        Ok(MrfV2Reader { readiness: self })
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MrfV2Reader {
    readiness: MrfV2Readiness,
}

impl MrfV2Reader {
    pub fn read(self, namespace: &str, data: &[u8]) -> std::result::Result<MrfV2Envelope, MrfV2Error> {
        if namespace != MRF_V2_NAMESPACE {
            return Err(MrfV2Error::UnsupportedNamespace {
                namespace: namespace.to_string(),
            });
        }
        if data.len() < MRF_V2_HEADER_LEN {
            return Err(MrfV2Error::Truncated);
        }
        if data[..4] != MRF_V2_MAGIC {
            return Err(MrfV2Error::InvalidMagic);
        }
        let format = LittleEndian::read_u16(&data[4..6]);
        if format != MRF_V2_FORMAT {
            return Err(MrfV2Error::UnsupportedFormat { format });
        }
        let version = LittleEndian::read_u16(&data[6..8]);
        if version != MRF_V2_VERSION || version != self.readiness.reader_version {
            return Err(MrfV2Error::UnsupportedVersion { version });
        }
        let min_reader_version = LittleEndian::read_u16(&data[8..10]);
        if min_reader_version > self.readiness.reader_version {
            return Err(MrfV2Error::RollbackFenced {
                min_reader_version,
                reader_version: self.readiness.reader_version,
            });
        }
        if min_reader_version > version {
            return Err(MrfV2Error::InvalidVersionRange {
                version,
                min_reader_version,
            });
        }
        let reserved = LittleEndian::read_u16(&data[10..12]);
        if reserved != 0 {
            return Err(MrfV2Error::ReservedHeaderBits { bits: reserved });
        }
        let capabilities = MrfV2Capabilities::from_bits(LittleEndian::read_u64(&data[12..20]))?;
        if !self.readiness.capabilities.supports(capabilities) {
            return Err(MrfV2Error::MissingCapabilities {
                required: capabilities.bits(),
                available: self.readiness.capabilities.bits(),
            });
        }
        let payload_len = LittleEndian::read_u32(&data[20..24]);
        let actual = data.len() - MRF_V2_HEADER_LEN;
        if usize::try_from(payload_len).map_err(|_| MrfV2Error::PayloadLengthMismatch {
            declared: payload_len,
            actual,
        })? != actual
        {
            return Err(MrfV2Error::PayloadLengthMismatch {
                declared: payload_len,
                actual,
            });
        }
        Ok(MrfV2Envelope {
            version,
            min_reader_version,
            capabilities,
            payload: data[MRF_V2_HEADER_LEN..].to_vec(),
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MrfV2Envelope {
    version: u16,
    min_reader_version: u16,
    capabilities: MrfV2Capabilities,
    payload: Vec<u8>,
}

impl MrfV2Envelope {
    pub const fn version(&self) -> u16 {
        self.version
    }

    pub const fn min_reader_version(&self) -> u16 {
        self.min_reader_version
    }

    pub const fn capabilities(&self) -> MrfV2Capabilities {
        self.capabilities
    }

    pub fn payload(&self) -> &[u8] {
        &self.payload
    }
}
pub fn encode_mrf_file(entries: &[MrfReplicateEntry]) -> Result<Vec<u8>> {
    let payload = rmp_serde::to_vec_named(entries).map_err(|e| ResyncStateError::Other(e.to_string()))?;
    let mut data = Vec::with_capacity(4 + payload.len());
    let mut fmt = [0u8; 2];
    LittleEndian::write_u16(&mut fmt, MRF_META_FORMAT);
    data.extend_from_slice(&fmt);
    let mut ver = [0u8; 2];
    LittleEndian::write_u16(&mut ver, MRF_META_VERSION);
    data.extend_from_slice(&ver);
    data.extend_from_slice(&payload);
    Ok(data)
}

pub fn decode_mrf_file(data: &[u8]) -> Result<Vec<MrfReplicateEntry>> {
    if data.len() <= 4 {
        return Err(ResyncStateError::CorruptedFormat);
    }
    let mut fmt = [0u8; 2];
    fmt.copy_from_slice(&data[0..2]);
    if LittleEndian::read_u16(&fmt) != MRF_META_FORMAT {
        return Err(ResyncStateError::CorruptedFormat);
    }
    let mut ver = [0u8; 2];
    ver.copy_from_slice(&data[2..4]);
    if LittleEndian::read_u16(&ver) != MRF_META_VERSION {
        return Err(ResyncStateError::CorruptedFormat);
    }
    rmp_serde::from_slice(&data[4..]).map_err(|e| ResyncStateError::Other(e.to_string()))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;
    use uuid::Uuid;

    // Capability word 31 = OperationKind | TargetArns | ForceDelete | DeleteMarkerMtime |
    // TargetDeleteMarkerVersionIds (backlog#2290).
    const ENVELOPE_FIXTURE: &[u8] = &[
        b'M', b'R', b'F', b'E', 1, 0, 1, 0, 1, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 2, 3,
    ];

    // The envelope a binary from before backlog#2290 writes: same header, capability word 15.
    const PRE_TARGET_MARKER_IDS_ENVELOPE_FIXTURE: &[u8] = &[
        b'M', b'R', b'F', b'E', 1, 0, 1, 0, 1, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 2, 3,
    ];

    #[test]
    fn mrf_file_round_trips_object_metadata_and_delete_entries() {
        let obj_vid = Uuid::new_v4();
        let del_vid = Uuid::new_v4();
        let entries = vec![
            MrfReplicateEntry {
                bucket: "bucket-a".to_string(),
                object: "metadata-a".to_string(),
                version_id: Some(obj_vid),
                retry_count: 1,
                size: 1024,
                op: MrfOpKind::Metadata,
                force_delete_id: None,
                force_delete_generation: None,
                force_delete_local_commit: false,
                force_delete: false,
                delete_marker_version_id: None,
                delete_marker: false,
                delete_marker_mtime: None,
                target_delete_marker_version_ids: HashMap::new(),
                target_delete_marker_version_ids_corrupt: false,
                target_arns: vec!["arn:target-a".to_string()],
            },
            MrfReplicateEntry {
                bucket: "bucket-a".to_string(),
                object: "object-a".to_string(),
                version_id: Some(obj_vid),
                retry_count: 2,
                size: 1024,
                op: MrfOpKind::Object,
                force_delete_id: None,
                force_delete_generation: None,
                force_delete_local_commit: false,
                force_delete: false,
                delete_marker_version_id: None,
                delete_marker: false,
                delete_marker_mtime: None,
                target_delete_marker_version_ids: HashMap::new(),
                target_delete_marker_version_ids_corrupt: false,
                target_arns: vec!["arn:target-a".to_string(), "arn:target-b".to_string()],
            },
            MrfReplicateEntry {
                bucket: "bucket-a".to_string(),
                object: "delete-a".to_string(),
                version_id: None,
                retry_count: 0,
                size: 0,
                op: MrfOpKind::Delete,
                force_delete_id: None,
                force_delete_generation: None,
                force_delete_local_commit: false,
                force_delete: true,
                delete_marker_version_id: Some(del_vid),
                delete_marker: true,
                delete_marker_mtime: Some(1_705_312_200_123_456_789),
                target_delete_marker_version_ids: HashMap::from([
                    ("arn:target-a".to_string(), "remote-marker-a".to_string()),
                    ("arn:target-b".to_string(), "remote-marker-b".to_string()),
                ]),
                target_delete_marker_version_ids_corrupt: false,
                target_arns: vec!["arn:target-a".to_string()],
            },
        ];

        let encoded = encode_mrf_file(&entries).expect("mrf file should encode");
        let decoded = decode_mrf_file(&encoded).expect("mrf file should decode");

        assert_eq!(decoded.len(), 3);
        assert_eq!(decoded[0].version_id, Some(obj_vid));
        assert_eq!(decoded[0].op, MrfOpKind::Metadata);
        assert_eq!(decoded[0].target_arns, vec!["arn:target-a".to_string()]);
        assert_eq!(decoded[0].delete_marker_mtime, None);
        assert!(!decoded[0].force_delete);
        assert_eq!(decoded[1].op, MrfOpKind::Object);
        assert!(!decoded[1].force_delete);
        assert_eq!(decoded[1].target_arns, vec!["arn:target-a".to_string(), "arn:target-b".to_string()]);
        assert_eq!(decoded[1].delete_marker_mtime, None);
        assert_eq!(decoded[2].delete_marker_version_id, Some(del_vid));
        assert_eq!(decoded[2].op, MrfOpKind::Delete);
        assert!(decoded[2].force_delete);
        assert_eq!(decoded[2].target_arns, vec!["arn:target-a".to_string()]);
        assert!(decoded[2].delete_marker);
        assert_eq!(
            decoded[2].delete_marker_mtime,
            Some(1_705_312_200_123_456_789),
            "delete-marker mtime must survive the MRF disk round-trip"
        );
        assert!(decoded[0].target_delete_marker_version_ids.is_empty());
        assert!(decoded[1].target_delete_marker_version_ids.is_empty());
        assert_eq!(
            decoded[2].target_delete_marker_version_ids,
            HashMap::from([
                ("arn:target-a".to_string(), "remote-marker-a".to_string()),
                ("arn:target-b".to_string(), "remote-marker-b".to_string()),
            ]),
            "target-assigned marker version ids must survive the MRF disk round-trip (backlog#2290)"
        );
        assert!(!decoded[2].target_delete_marker_version_ids_corrupt);
    }

    /// backlog#2290: the corrupt flag rides the same journal round trip, and an
    /// entry that carries neither field encodes exactly as it did before the
    /// field existed (both keys are skipped when empty/false).
    #[test]
    fn mrf_file_round_trips_target_marker_ids_corrupt_flag_and_skips_empty_keys() {
        let corrupt = MrfReplicateEntry {
            bucket: "bucket-a".to_string(),
            object: "delete-a".to_string(),
            op: MrfOpKind::Delete,
            delete_marker: true,
            delete_marker_version_id: Some(Uuid::new_v4()),
            target_delete_marker_version_ids_corrupt: true,
            target_arns: vec!["arn:target-a".to_string()],
            ..Default::default()
        };
        let decoded = decode_mrf_file(&encode_mrf_file(std::slice::from_ref(&corrupt)).expect("mrf file should encode"))
            .expect("mrf file should decode");
        assert_eq!(decoded, vec![corrupt]);
        assert!(decoded[0].target_delete_marker_version_ids_corrupt);

        let plain = MrfReplicateEntry {
            bucket: "bucket-a".to_string(),
            object: "delete-a".to_string(),
            op: MrfOpKind::Delete,
            delete_marker: true,
            target_arns: vec!["arn:target-a".to_string()],
            ..Default::default()
        };
        let encoded = encode_mrf_file(std::slice::from_ref(&plain)).expect("mrf file should encode");
        let payload = String::from_utf8_lossy(&encoded);
        assert!(
            !payload.contains("targetDeleteMarkerVersionIDs"),
            "an entry without recorded ids must not grow the new keys: {payload}"
        );
        assert_eq!(decode_mrf_file(&encoded).expect("mrf file should decode"), vec![plain]);
    }

    #[test]
    fn mrf_legacy_file_without_op_decodes_as_object() {
        let mut payload = Vec::new();
        rmp::encode::write_array_len(&mut payload, 1).expect("array len should encode");
        rmp::encode::write_map_len(&mut payload, 4).expect("map len should encode");
        rmp::encode::write_str(&mut payload, "bucket").expect("bucket key should encode");
        rmp::encode::write_str(&mut payload, "old-bucket").expect("bucket value should encode");
        rmp::encode::write_str(&mut payload, "object").expect("object key should encode");
        rmp::encode::write_str(&mut payload, "old-key").expect("object value should encode");
        rmp::encode::write_str(&mut payload, "retryCount").expect("retry key should encode");
        rmp::encode::write_i32(&mut payload, 2).expect("retry value should encode");
        rmp::encode::write_str(&mut payload, "size").expect("size key should encode");
        rmp::encode::write_i64(&mut payload, 100).expect("size value should encode");

        let mut data = Vec::with_capacity(4 + payload.len());
        data.extend_from_slice(&MRF_META_FORMAT.to_le_bytes());
        data.extend_from_slice(&MRF_META_VERSION.to_le_bytes());
        data.extend_from_slice(&payload);

        let decoded = decode_mrf_file(&data).expect("legacy mrf file should decode");

        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].bucket, "old-bucket");
        assert_eq!(decoded[0].object, "old-key");
        assert_eq!(decoded[0].retry_count, 2);
        assert_eq!(decoded[0].size, 100);
        assert_eq!(decoded[0].op, MrfOpKind::Object);
        assert!(decoded[0].target_arns.is_empty());
        assert!(!decoded[0].force_delete);
        // Old files lack the deleteMarkerMtime key; it must default to None so replay keeps the
        // pre-#867 fallback to the current time.
        assert_eq!(decoded[0].delete_marker_mtime, None);
        // Old files also lack the target marker id keys; they must default to an empty map
        // and a clear corrupt flag so replay keeps the pre-#2290 source-id fallback.
        assert!(decoded[0].target_delete_marker_version_ids.is_empty());
        assert!(!decoded[0].target_delete_marker_version_ids_corrupt);
    }

    /// backlog#2290: a delete-marker entry written by a binary that predates the
    /// `targetDeleteMarkerVersionIDs` key decodes with an empty map and a clear
    /// corrupt flag — the exact shape replay handled before the field existed.
    #[test]
    fn mrf_pre_target_marker_ids_delete_entry_decodes_with_empty_map() {
        let marker_version_id = Uuid::new_v4();
        let mut payload = Vec::new();
        rmp::encode::write_array_len(&mut payload, 1).expect("array len should encode");
        rmp::encode::write_map_len(&mut payload, 9).expect("map len should encode");
        rmp::encode::write_str(&mut payload, "bucket").expect("bucket key should encode");
        rmp::encode::write_str(&mut payload, "old-bucket").expect("bucket value should encode");
        rmp::encode::write_str(&mut payload, "object").expect("object key should encode");
        rmp::encode::write_str(&mut payload, "old-key").expect("object value should encode");
        rmp::encode::write_str(&mut payload, "retryCount").expect("retry key should encode");
        rmp::encode::write_i32(&mut payload, 0).expect("retry value should encode");
        rmp::encode::write_str(&mut payload, "size").expect("size key should encode");
        rmp::encode::write_i64(&mut payload, 0).expect("size value should encode");
        rmp::encode::write_str(&mut payload, "op").expect("op key should encode");
        rmp::encode::write_str(&mut payload, "delete").expect("op value should encode");
        rmp::encode::write_str(&mut payload, "forceDelete").expect("forceDelete key should encode");
        rmp::encode::write_bool(&mut payload, false).expect("forceDelete value should encode");
        rmp::encode::write_str(&mut payload, "deleteMarkerVersionID").expect("marker id key should encode");
        // Uuid serializes as a 16-byte bin in the MessagePack journal.
        rmp::encode::write_bin(&mut payload, marker_version_id.as_bytes()).expect("marker id value should encode");
        rmp::encode::write_str(&mut payload, "deleteMarker").expect("deleteMarker key should encode");
        rmp::encode::write_bool(&mut payload, true).expect("deleteMarker value should encode");
        rmp::encode::write_str(&mut payload, "targetARNs").expect("targetARNs key should encode");
        rmp::encode::write_array_len(&mut payload, 1).expect("targetARNs len should encode");
        rmp::encode::write_str(&mut payload, "arn:target-a").expect("targetARNs value should encode");

        let mut data = Vec::with_capacity(4 + payload.len());
        data.extend_from_slice(&MRF_META_FORMAT.to_le_bytes());
        data.extend_from_slice(&MRF_META_VERSION.to_le_bytes());
        data.extend_from_slice(&payload);

        let decoded = decode_mrf_file(&data).expect("pre-#2290 delete-marker entry should decode");

        assert_eq!(decoded.len(), 1);
        assert_eq!(decoded[0].op, MrfOpKind::Delete);
        assert!(decoded[0].delete_marker);
        assert_eq!(decoded[0].delete_marker_version_id, Some(marker_version_id));
        assert_eq!(decoded[0].target_arns, vec!["arn:target-a".to_string()]);
        assert!(decoded[0].target_delete_marker_version_ids.is_empty());
        assert!(!decoded[0].target_delete_marker_version_ids_corrupt);
    }

    /// backlog#2290: the new field is fenced by its own capability bit exactly
    /// like the earlier optional fields — a reader without the bit refuses an
    /// envelope that advertises it, while the current reader still accepts the
    /// pre-#2290 envelope.
    #[test]
    fn envelope_target_marker_ids_capability_is_fenced_and_backward_compatible() {
        assert!(MrfCapabilities::current().contains(MrfCapability::TargetDeleteMarkerVersionIds));
        assert_eq!(MrfCapabilities::with(MrfCapability::TargetDeleteMarkerVersionIds).bits(), 1 << 4);

        // Old envelope, current reader: accepted, and the negotiated set lacks the new bit.
        let legacy = MrfEnvelope::decode(PRE_TARGET_MARKER_IDS_ENVELOPE_FIXTURE, MrfProtocolCapabilities::current())
            .expect("pre-#2290 envelope should decode");
        assert_eq!(legacy.protocol().capabilities().bits(), 15);
        assert!(
            !legacy
                .protocol()
                .capabilities()
                .contains(MrfCapability::TargetDeleteMarkerVersionIds)
        );
        assert_eq!(legacy.payload(), &[1, 2, 3]);

        // Current envelope, reader that only knows the pre-#2290 bits: refused.
        let pre_2290_reader = MrfProtocolCapabilities::new(1, 1, MrfCapabilities::from_bits(15).expect("known bits"));
        assert_eq!(
            MrfEnvelope::decode(ENVELOPE_FIXTURE, pre_2290_reader),
            Err(MrfEnvelopeError::MissingCapabilities {
                required: 31,
                available: 15,
            })
        );

        // Negotiation with such a peer drops the bit instead of failing.
        let negotiated = MrfProtocolCapabilities::current()
            .negotiate(pre_2290_reader)
            .expect("negotiation with a pre-#2290 peer should succeed");
        assert!(
            !negotiated
                .capabilities()
                .contains(MrfCapability::TargetDeleteMarkerVersionIds)
        );
        assert!(negotiated.capabilities().contains(MrfCapability::DeleteMarkerMtime));
    }

    #[test]
    fn mrf_file_defaults_missing_force_delete_to_false() {
        let mut payload = Vec::new();
        rmp::encode::write_array_len(&mut payload, 1).expect("array len should encode");
        rmp::encode::write_map_len(&mut payload, 5).expect("map len should encode");
        rmp::encode::write_str(&mut payload, "bucket").expect("bucket key should encode");
        rmp::encode::write_str(&mut payload, "old-bucket").expect("bucket value should encode");
        rmp::encode::write_str(&mut payload, "object").expect("object key should encode");
        rmp::encode::write_str(&mut payload, "old-key").expect("object value should encode");
        rmp::encode::write_str(&mut payload, "retryCount").expect("retry key should encode");
        rmp::encode::write_i32(&mut payload, 1).expect("retry value should encode");
        rmp::encode::write_str(&mut payload, "size").expect("size key should encode");
        rmp::encode::write_i64(&mut payload, 0).expect("size value should encode");
        rmp::encode::write_str(&mut payload, "op").expect("op key should encode");
        rmp::encode::write_str(&mut payload, "delete").expect("op value should encode");

        let mut data = Vec::with_capacity(4 + payload.len());
        data.extend_from_slice(&MRF_META_FORMAT.to_le_bytes());
        data.extend_from_slice(&MRF_META_VERSION.to_le_bytes());
        data.extend_from_slice(&payload);

        let decoded = decode_mrf_file(&data).expect("MRF payload should decode");

        assert_eq!(decoded[0].op, MrfOpKind::Delete);
        assert!(!decoded[0].force_delete);
    }

    #[test]
    fn mrf_file_rejects_invalid_header() {
        let mut data = Vec::new();
        data.extend_from_slice(&2u16.to_le_bytes());
        data.extend_from_slice(&MRF_META_VERSION.to_le_bytes());
        data.push(0x90);

        assert!(matches!(decode_mrf_file(&data), Err(ResyncStateError::CorruptedFormat)));
    }

    #[test]
    fn envelope_fixture_is_stable_and_round_trips() {
        let envelope =
            MrfEnvelope::new(MrfProtocolCapabilities::current(), vec![1, 2, 3]).expect("current MRF envelope should be valid");
        assert_eq!(envelope.encode().expect("envelope should encode"), ENVELOPE_FIXTURE);
        let decoded = MrfEnvelope::decode(ENVELOPE_FIXTURE, MrfProtocolCapabilities::current()).expect("fixture should decode");
        assert_eq!(decoded.protocol(), MrfProtocolCapabilities::current());
        assert_eq!(decoded.payload(), &[1, 2, 3]);
    }

    #[test]
    fn envelope_accepts_a_forward_compatible_writer_version() {
        let mut version = ENVELOPE_FIXTURE.to_vec();
        version[6..8].copy_from_slice(&2u16.to_le_bytes());
        let decoded =
            MrfEnvelope::decode(&version, MrfProtocolCapabilities::current()).expect("compatible v2 envelope should decode");
        assert_eq!(decoded.protocol().version(), 2);
        assert_eq!(decoded.protocol().min_reader_version(), 1);

        let mut fenced = version;
        fenced[8..10].copy_from_slice(&2u16.to_le_bytes());
        assert_eq!(
            MrfEnvelope::decode(&fenced, MrfProtocolCapabilities::current()),
            Err(MrfEnvelopeError::RollbackFenced {
                min_reader_version: 2,
                supported_version: 1,
            })
        );
    }

    #[test]
    fn envelope_rejects_unknown_capability_bits() {
        let mut capabilities = ENVELOPE_FIXTURE.to_vec();
        capabilities[12..20].copy_from_slice(&(1u64 << 63).to_le_bytes());

        assert_eq!(
            MrfEnvelope::decode(&capabilities, MrfProtocolCapabilities::current()),
            Err(MrfEnvelopeError::UnknownCapabilities { bits: 1u64 << 63 })
        );

        let mut reserved = ENVELOPE_FIXTURE.to_vec();
        reserved[10..12].copy_from_slice(&1u16.to_le_bytes());
        assert_eq!(
            MrfEnvelope::decode(&reserved, MrfProtocolCapabilities::current()),
            Err(MrfEnvelopeError::ReservedHeaderBits { bits: 1 })
        );

        let mut legacy = ENVELOPE_FIXTURE.to_vec();
        legacy[6..8].copy_from_slice(&0u16.to_le_bytes());
        assert_eq!(
            MrfEnvelope::decode(&legacy, MrfProtocolCapabilities::current()),
            Err(MrfEnvelopeError::UnsupportedVersion { version: 0 })
        );
    }

    #[test]
    fn envelope_rejects_rollback_and_missing_capabilities() {
        let mut rollback = ENVELOPE_FIXTURE.to_vec();
        rollback[8..10].copy_from_slice(&2u16.to_le_bytes());
        assert_eq!(
            MrfEnvelope::decode(&rollback, MrfProtocolCapabilities::current()),
            Err(MrfEnvelopeError::RollbackFenced {
                min_reader_version: 2,
                supported_version: 1,
            })
        );

        let required = MrfCapabilities::with(MrfCapability::TargetArns);
        let envelope =
            MrfEnvelope::new(MrfProtocolCapabilities::new(1, 1, required), Vec::new()).expect("known capability should be valid");
        let encoded = envelope.encode().expect("envelope should encode");
        assert_eq!(
            MrfEnvelope::decode(&encoded, MrfProtocolCapabilities::new(1, 1, MrfCapabilities::empty())),
            Err(MrfEnvelopeError::MissingCapabilities {
                required: required.bits(),
                available: 0,
            })
        );
    }

    #[test]
    fn protocol_negotiation_fences_rollback() {
        let current = MrfProtocolCapabilities::current();
        let rollback = MrfProtocolCapabilities::new(1, 2, MrfCapabilities::current());
        assert_eq!(
            current.negotiate(rollback),
            Err(MrfEnvelopeError::InvalidVersionRange {
                version: 1,
                min_reader_version: 2,
            })
        );
    }

    #[test]
    fn protocol_negotiation_rejects_invalid_local_version_range() {
        let invalid = MrfProtocolCapabilities::new(1, 2, MrfCapabilities::current());
        assert_eq!(
            invalid.negotiate(MrfProtocolCapabilities::current()),
            Err(MrfEnvelopeError::InvalidVersionRange {
                version: 1,
                min_reader_version: 2,
            })
        );
    }

    #[test]
    fn protocol_negotiation_intersects_capabilities() {
        let local = MrfProtocolCapabilities::new(1, 1, MrfCapabilities::with(MrfCapability::TargetArns));
        let peer = MrfProtocolCapabilities::new(1, 1, MrfCapabilities::with(MrfCapability::ForceDelete));
        let negotiated = local.negotiate(peer).expect("same-version peers should negotiate");
        assert_eq!(negotiated.capabilities(), MrfCapabilities::empty());
    }

    #[test]
    fn protocol_negotiation_accepts_a_forward_compatible_peer() {
        let reader = MrfProtocolCapabilities::current();
        let writer = MrfProtocolCapabilities::new(2, 1, MrfCapabilities::current());
        let negotiated = reader
            .negotiate(writer)
            .expect("v1 reader should negotiate with a compatible v2 writer");
        assert_eq!(negotiated.version(), 1);
        assert_eq!(negotiated.min_reader_version(), 1);
    }

    #[test]
    fn v1_file_contract_and_v2_namespace_remain_separate() {
        assert_eq!(encode_mrf_file(&[]).expect("v1 empty file should encode"), vec![1, 0, 1, 0, 0x90]);
        assert_eq!(MRF_V2_FILE, "config/replication-v2/mrf.bin");
        assert_ne!(MRF_V2_FILE, "config/replication/mrf.bin");
    }

    #[test]
    fn dormant_v2_reader_accepts_stable_fixture() {
        let fixture = [
            b'M', b'R', b'F', b'2', 2, 0, 2, 0, 2, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 2, 3,
        ];
        let readiness = MrfV2Readiness::dormant();
        assert!(!readiness.writer_enabled());
        let envelope = readiness
            .reader()
            .expect("dormant readiness should expose the reader")
            .read(MRF_V2_NAMESPACE, &fixture)
            .expect("v2 fixture should decode");
        assert_eq!(envelope.version(), MRF_V2_VERSION);
        assert_eq!(envelope.min_reader_version(), MRF_V2_VERSION);
        assert_eq!(envelope.capabilities(), MrfV2Capabilities::current());
        assert_eq!(envelope.payload(), &[1, 2, 3]);
    }

    #[test]
    fn v2_reader_rejects_wrong_namespace_version_capability_and_rollback() {
        let fixture = [
            b'M', b'R', b'F', b'2', 2, 0, 2, 0, 2, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 2, 3,
        ];
        let reader = MrfV2Readiness::dormant().reader().expect("dormant reader should initialize");
        assert!(matches!(
            reader.read("config/replication/mrf.bin", &fixture),
            Err(MrfV2Error::UnsupportedNamespace { .. })
        ));

        let mut version = fixture;
        version[6..8].copy_from_slice(&1u16.to_le_bytes());
        assert_eq!(
            reader.read(MRF_V2_NAMESPACE, &version),
            Err(MrfV2Error::UnsupportedVersion { version: 1 })
        );

        let mut capabilities = fixture;
        capabilities[12..20].copy_from_slice(&(1u64 << 63).to_le_bytes());
        assert_eq!(
            reader.read(MRF_V2_NAMESPACE, &capabilities),
            Err(MrfV2Error::UnknownCapabilities { bits: 1u64 << 63 })
        );

        let mut rollback = fixture;
        rollback[8..10].copy_from_slice(&3u16.to_le_bytes());
        assert_eq!(
            reader.read(MRF_V2_NAMESPACE, &rollback),
            Err(MrfV2Error::RollbackFenced {
                min_reader_version: 3,
                reader_version: 2,
            })
        );
    }

    #[test]
    fn v2_readiness_rejects_writer_enablement_and_missing_capabilities() {
        assert_eq!(
            MrfV2Readiness::new(2, MrfV2Capabilities::current(), true).reader(),
            Err(MrfV2Error::WriterEnabled)
        );

        let fixture = [
            b'M', b'R', b'F', b'2', 2, 0, 2, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1, 2, 3,
        ];
        let reader = MrfV2Readiness::new(2, MrfV2Capabilities::empty(), false)
            .reader()
            .expect("readiness with valid empty capabilities should initialize");
        assert_eq!(
            reader.read(MRF_V2_NAMESPACE, &fixture),
            Err(MrfV2Error::MissingCapabilities {
                required: 1,
                available: 0,
            })
        );
    }
}
