// 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 std::collections::HashMap;

use crate::http::{
    AMZ_BUCKET_REPLICATION_STATUS, AMZ_OBJECT_TAGGING, SSEC_ALGORITHM_HEADER, SSEC_KEY_HEADER, SSEC_KEY_MD5_HEADER,
    SUFFIX_REPLICATION_RESET_STATUS, SUFFIX_REPLICATION_STATUS, get_consistent_internal_metadata, get_header_metadata,
};
use s3s::dto::ReplicationConfiguration;
use time::OffsetDateTime;
use uuid::Uuid;

use crate::storage_api::ObjectToDelete;
use crate::{
    ObjectOpts, ReplicateDecision, ReplicateTargetDecision, ReplicationConfigurationExt as _, ReplicationState,
    ReplicationStatusType, ReplicationType, ResyncTargetDecision, VersionPurgeStatusType, replication_statuses_map,
    target_reset_header, version_purge_statuses_map,
};

#[derive(Debug, Clone, Default)]
pub struct MustReplicateOptions {
    meta: HashMap<String, String>,
    status: ReplicationStatusType,
    op_type: ReplicationType,
    replication_request: bool,
}

impl MustReplicateOptions {
    pub fn new(meta: &HashMap<String, String>, user_tags: String, op_type: ReplicationType, replication_request: bool) -> Self {
        let mut meta = meta.clone();
        if !user_tags.is_empty() {
            meta.insert(AMZ_OBJECT_TAGGING.to_string(), user_tags);
        }

        Self {
            meta,
            status: ReplicationStatusType::default(),
            op_type,
            replication_request,
        }
    }

    pub fn with_replication_status(mut self, status: ReplicationStatusType) -> Self {
        self.status = status;
        self
    }

    pub fn replication_status(&self) -> ReplicationStatusType {
        if let Some(rs) = self.meta.get(AMZ_BUCKET_REPLICATION_STATUS) {
            let status = ReplicationStatusType::from(rs.as_str());
            if !status.is_empty() {
                return status;
            }
        }
        self.status.clone()
    }

    pub fn is_existing_object_replication(&self) -> bool {
        self.op_type == ReplicationType::ExistingObject
    }

    pub fn is_metadata_replication(&self) -> bool {
        self.op_type == ReplicationType::Metadata
    }

    pub fn is_replication_request(&self) -> bool {
        self.replication_request
    }

    pub fn user_tags(&self) -> &str {
        self.meta.get(AMZ_OBJECT_TAGGING).map(String::as_str).unwrap_or_default()
    }

    pub fn metadata_target_is_eligible(&self, arn: &str) -> bool {
        if !self.is_metadata_replication() {
            return true;
        }

        // A REPLICA version was delivered by a peer and carries no per-target
        // internal status of its own; whether its local metadata edits flow
        // back is the replication rule's ReplicaModifications decision
        // (`ReplicationConfig::replicate` with `replica = true`, MinIO
        // mustReplicate parity). Gating it on a COMPLETED target state would
        // silently drop every replica-side tag / retention / legal-hold edit in
        // an active-active topology (rustfs/backlog#1953).
        if self.replication_status() == ReplicationStatusType::Replica {
            return true;
        }

        let Some(statuses) = get_consistent_internal_metadata(&self.meta, SUFFIX_REPLICATION_STATUS) else {
            return false;
        };
        let mut admitted = None;
        for entry in statuses.split(';').filter(|entry| !entry.is_empty()) {
            let Some((target_arn, status)) = entry.split_once('=') else {
                continue;
            };
            if target_arn != arn {
                continue;
            }
            // Per-target admission is authoritative only when the target has
            // exactly one unambiguous historical status record.
            if admitted.is_some() {
                return false;
            }
            admitted = Some(ReplicationStatusType::from(status));
        }

        matches!(
            admitted,
            Some(
                ReplicationStatusType::Pending
                    | ReplicationStatusType::Failed
                    | ReplicationStatusType::Completed
                    | ReplicationStatusType::CompletedLegacy
            )
        )
    }
}

pub fn is_ssec_encrypted(user_defined: &HashMap<String, String>) -> bool {
    user_defined.contains_key(SSEC_ALGORITHM_HEADER)
        || user_defined.contains_key(SSEC_KEY_HEADER)
        || user_defined.contains_key(SSEC_KEY_MD5_HEADER)
}

#[derive(Debug, Clone)]
pub struct ReplicationDeleteSource<'a> {
    pub user_defined: &'a HashMap<String, String>,
    pub user_tags: &'a str,
    pub delete_marker: bool,
    pub replication_status: ReplicationStatusType,
}

pub fn delete_replication_object_opts(dobj: &ObjectToDelete, source: &ReplicationDeleteSource<'_>) -> ObjectOpts {
    ObjectOpts {
        name: dobj.object_name.clone(),
        ssec: is_ssec_encrypted(source.user_defined),
        user_tags: source.user_tags.to_string(),
        delete_marker: source.delete_marker,
        version_id: dobj.version_id,
        op_type: ReplicationType::Delete,
        replica: source.replication_status == ReplicationStatusType::Replica,
        ..Default::default()
    }
}

#[derive(Debug, Clone)]
pub struct ReplicationDeleteStateSource {
    pub name: String,
    pub user_tags: String,
    pub version_id: Option<Uuid>,
    pub delete_marker: bool,
    pub replica: bool,
}

pub fn delete_replication_state_from_config(
    config: &ReplicationConfiguration,
    source: &ReplicationDeleteStateSource,
) -> Option<ReplicationState> {
    let opts = ObjectOpts {
        name: source.name.clone(),
        user_tags: source.user_tags.clone(),
        version_id: source.version_id,
        delete_marker: source.delete_marker,
        op_type: ReplicationType::Delete,
        replica: source.replica,
        ..Default::default()
    };
    let target_decisions = config.filter_target_replication_decisions(&opts);
    if target_decisions.is_empty() {
        return None;
    }

    let mut decision = ReplicateDecision::new();
    for (target_arn, replicate) in target_decisions {
        decision.set(ReplicateTargetDecision::new(target_arn, replicate, false));
    }
    if !decision.replicate_any() {
        return None;
    }

    let pending_status = decision.pending_status();
    let mut state = ReplicationState {
        replicate_decision_str: decision.to_string(),
        ..Default::default()
    };
    if source.version_id.is_some() {
        state.version_purge_status_internal = pending_status.clone();
        state.purge_targets = version_purge_statuses_map(pending_status.as_deref().unwrap_or_default());
    } else {
        state.replication_status_internal = pending_status.clone();
        state.targets = replication_statuses_map(pending_status.as_deref().unwrap_or_default());
    }
    Some(state)
}

#[derive(Debug, Clone, Copy)]
pub struct ReplicationDeleteScheduleInput<'a> {
    pub replication_request: bool,
    pub version_id_requested: bool,
    pub source_delete_marker: bool,
    pub source_replication_status: &'a ReplicationStatusType,
    pub source_version_purge_status: &'a VersionPurgeStatusType,
    pub deleted_delete_marker_version: bool,
}

pub fn should_schedule_delete_replication(input: ReplicationDeleteScheduleInput<'_>) -> bool {
    if input.replication_request {
        return false;
    }

    if input.version_id_requested {
        return input.source_version_purge_status == &VersionPurgeStatusType::Pending;
    }

    input.source_replication_status == &ReplicationStatusType::Replica
        || input.source_replication_status == &ReplicationStatusType::Pending
        || input.source_version_purge_status == &VersionPurgeStatusType::Pending
        || (input.deleted_delete_marker_version && input.source_replication_status == &ReplicationStatusType::Completed)
}

pub fn delete_replication_version_id(
    source_delete_marker: bool,
    source_version_id: Option<Uuid>,
    deleted_delete_marker_version: bool,
) -> Option<Uuid> {
    if source_delete_marker && !deleted_delete_marker_version {
        None
    } else {
        source_version_id
    }
}

pub fn should_use_existing_delete_replication_source(
    replication_request: bool,
    deleted_delete_marker: bool,
    has_existing_source: bool,
) -> bool {
    replication_request && deleted_delete_marker && has_existing_source
}

pub fn should_use_existing_delete_replication_info(version_id_requested: bool, delete_marker_request: bool) -> bool {
    version_id_requested && !delete_marker_request
}

pub fn heal_uses_delete_replication_path(delete_marker: bool, version_purge_status: &VersionPurgeStatusType) -> bool {
    delete_marker || !version_purge_status.is_empty()
}

pub fn delete_replication_missing_source_decision(
    delete_marker: bool,
    target_status: ReplicationStatusType,
    rule_replicates: bool,
    version_purge_status: &VersionPurgeStatusType,
) -> Option<bool> {
    let valid_replication_status = matches!(
        target_status,
        ReplicationStatusType::Pending | ReplicationStatusType::Completed | ReplicationStatusType::Failed
    );

    if delete_marker && (valid_replication_status || rule_replicates) {
        return Some(rule_replicates);
    }

    if !version_purge_status.is_empty() {
        return Some(matches!(
            version_purge_status,
            &VersionPurgeStatusType::Pending | &VersionPurgeStatusType::Failed
        ));
    }

    None
}

#[derive(Debug, Clone)]
pub struct ReplicationResyncTargetObject<'a> {
    pub mod_time: Option<OffsetDateTime>,
    pub user_defined: &'a HashMap<String, String>,
}

pub fn resync_target_for_object(
    object: &ReplicationResyncTargetObject<'_>,
    arn: &str,
    reset_id: &str,
    reset_before_date: Option<OffsetDateTime>,
    status: ReplicationStatusType,
) -> ResyncTargetDecision {
    let rs = object
        .user_defined
        .get(target_reset_header(arn).as_str())
        .cloned()
        .or_else(|| get_header_metadata(object.user_defined, SUFFIX_REPLICATION_RESET_STATUS));

    let mut dec = ResyncTargetDecision::default();

    let mod_time = object.mod_time.unwrap_or(OffsetDateTime::UNIX_EPOCH);

    let Some(rs) = rs else {
        let reset_before_date = reset_before_date.unwrap_or(OffsetDateTime::UNIX_EPOCH);
        if !reset_id.is_empty() && mod_time <= reset_before_date {
            dec.replicate = true;
            return dec;
        }

        dec.replicate = status == ReplicationStatusType::Empty;

        return dec;
    };

    let Some(reset_before_date) = reset_before_date else {
        return dec;
    };

    if reset_id.is_empty() {
        return dec;
    }

    let parts: Vec<&str> = rs.splitn(2, ';').collect();

    if parts.len() != 2 {
        return dec;
    }

    let new_reset = parts[1] != reset_id;

    if !new_reset && status == ReplicationStatusType::Completed {
        return dec;
    }

    dec.replicate = new_reset && mod_time <= reset_before_date;

    dec
}

#[cfg(test)]
mod tests {
    use super::{
        MustReplicateOptions, ReplicationDeleteScheduleInput, ReplicationDeleteSource, ReplicationDeleteStateSource,
        ReplicationResyncTargetObject, delete_replication_missing_source_decision, delete_replication_object_opts,
        delete_replication_state_from_config, delete_replication_version_id, heal_uses_delete_replication_path,
        is_ssec_encrypted, resync_target_for_object, should_schedule_delete_replication,
        should_use_existing_delete_replication_info, should_use_existing_delete_replication_source,
    };
    use crate::http::{
        AMZ_BUCKET_REPLICATION_STATUS, SSEC_ALGORITHM_HEADER, SUFFIX_REPLICATION_STATUS, insert_internal_metadata,
    };
    use crate::storage_api::ObjectToDelete;
    use crate::{ReplicationStatusType, ReplicationType, VersionPurgeStatusType, target_reset_header};
    use s3s::dto::{
        DeleteMarkerReplication, DeleteMarkerReplicationStatus, DeleteReplication, DeleteReplicationStatus, Destination,
        ExistingObjectReplication, ExistingObjectReplicationStatus, ReplicaModifications, ReplicaModificationsStatus,
        ReplicationConfiguration, ReplicationRule, ReplicationRuleStatus, SourceSelectionCriteria,
    };
    use std::collections::HashMap;
    use time::{Duration, OffsetDateTime};
    use uuid::Uuid;

    #[test]
    fn must_replicate_options_preserves_user_tags_and_operation_type() {
        let options = MustReplicateOptions::new(&HashMap::new(), "env=prod".to_string(), ReplicationType::ExistingObject, true);

        assert_eq!(options.user_tags(), "env=prod");
        assert!(options.is_existing_object_replication());
        assert!(options.is_replication_request());
    }

    #[test]
    fn must_replicate_options_reads_replication_status_header() {
        let meta = HashMap::from([(AMZ_BUCKET_REPLICATION_STATUS.to_string(), "COMPLETED".to_string())]);
        let options = MustReplicateOptions::new(&meta, String::new(), ReplicationType::Object, false)
            .with_replication_status(ReplicationStatusType::Replica);

        assert_eq!(options.replication_status(), ReplicationStatusType::Completed);
    }

    #[test]
    fn must_replicate_options_falls_back_to_explicit_replication_status() {
        let options = MustReplicateOptions::new(&HashMap::new(), String::new(), ReplicationType::Delete, false)
            .with_replication_status(ReplicationStatusType::Replica);

        assert_eq!(options.replication_status(), ReplicationStatusType::Replica);
    }

    #[test]
    fn metadata_replication_requires_prior_target_admission() {
        let arn = "arn:rustfs:replication:target";
        for status in ["PENDING", "FAILED", "COMPLETED", "COMPLETE"] {
            let mut meta = HashMap::new();
            insert_internal_metadata(&mut meta, SUFFIX_REPLICATION_STATUS, format!("{arn}={status};"));
            let options = MustReplicateOptions::new(&meta, String::new(), ReplicationType::Metadata, false);

            assert!(
                options.metadata_target_is_eligible(arn),
                "known target state {status} must remain eligible"
            );
            assert!(!options.metadata_target_is_eligible("arn:rustfs:replication:missing"));
        }

        for status in ["", "REPLICA", "UNKNOWN"] {
            let mut meta = HashMap::new();
            insert_internal_metadata(&mut meta, SUFFIX_REPLICATION_STATUS, format!("{arn}={status};"));
            let options = MustReplicateOptions::new(&meta, String::new(), ReplicationType::Metadata, false);
            assert!(
                !options.metadata_target_is_eligible(arn),
                "unsupported target state {status:?} must fail closed"
            );
        }

        for key in ["x-rustfs-internal-replication-status", "X-Minio-Internal-Replication-Status"] {
            let meta = HashMap::from([(key.to_string(), format!("{arn}=PENDING;"))]);
            assert!(
                MustReplicateOptions::new(&meta, String::new(), ReplicationType::Metadata, false)
                    .metadata_target_is_eligible(arn),
                "a single compatibility alias must preserve rolling-upgrade admission"
            );
        }

        let matching_aliases = HashMap::from([
            ("x-rustfs-internal-replication-status".to_string(), format!("{arn}=COMPLETED;")),
            ("X-Minio-Internal-Replication-Status".to_string(), format!("{arn}=COMPLETED;")),
        ]);
        assert!(
            MustReplicateOptions::new(&matching_aliases, String::new(), ReplicationType::Metadata, false)
                .metadata_target_is_eligible(arn)
        );

        for conflicting in [
            HashMap::from([
                ("x-rustfs-internal-replication-status".to_string(), format!("{arn}=PENDING;")),
                ("x-minio-internal-replication-status".to_string(), format!("{arn}=REPLICA;")),
            ]),
            HashMap::from([
                ("x-rustfs-internal-replication-status".to_string(), format!("{arn}=PENDING;")),
                ("x-minio-internal-replication-status".to_string(), String::new()),
            ]),
            HashMap::from([(
                "x-rustfs-internal-replication-status".to_string(),
                format!("{arn}=PENDING;{arn}=REPLICA;"),
            )]),
            HashMap::from([(
                "x-rustfs-internal-replication-status".to_string(),
                format!("{arn}=PENDING;{arn}=PENDING;"),
            )]),
        ] {
            assert!(
                !MustReplicateOptions::new(&conflicting, String::new(), ReplicationType::Metadata, false)
                    .metadata_target_is_eligible(arn),
                "conflicting, empty, or duplicate admission evidence must fail closed"
            );
        }

        // A replica-side metadata edit (active-active) has no per-target
        // internal status; eligibility is left to the ReplicaModifications rule.
        let replica = MustReplicateOptions::new(&HashMap::new(), String::new(), ReplicationType::Metadata, false)
            .with_replication_status(ReplicationStatusType::Replica);
        assert!(replica.metadata_target_is_eligible(arn));
        assert!(replica.metadata_target_is_eligible("arn:rustfs:replication:missing"));
        assert!(
            MustReplicateOptions::new(&HashMap::new(), String::new(), ReplicationType::Object, false)
                .metadata_target_is_eligible(arn)
        );
    }

    #[test]
    fn ssec_detection_uses_existing_metadata_headers() {
        let meta = HashMap::from([(SSEC_ALGORITHM_HEADER.to_string(), "AES256".to_string())]);

        assert!(is_ssec_encrypted(&meta));
        assert!(!is_ssec_encrypted(&HashMap::new()));
    }

    #[test]
    fn heal_delete_path_is_limited_to_delete_markers_and_version_purges() {
        assert!(heal_uses_delete_replication_path(true, &VersionPurgeStatusType::Empty));
        assert!(heal_uses_delete_replication_path(false, &VersionPurgeStatusType::Pending));
        assert!(!heal_uses_delete_replication_path(false, &VersionPurgeStatusType::Empty));
    }

    #[test]
    fn missing_source_delete_decision_keeps_delete_marker_and_purge_rules() {
        assert_eq!(
            delete_replication_missing_source_decision(
                true,
                ReplicationStatusType::Completed,
                false,
                &VersionPurgeStatusType::Empty,
            ),
            Some(false)
        );
        assert_eq!(
            delete_replication_missing_source_decision(
                false,
                ReplicationStatusType::Empty,
                false,
                &VersionPurgeStatusType::Pending,
            ),
            Some(true)
        );
        assert_eq!(
            delete_replication_missing_source_decision(
                false,
                ReplicationStatusType::Empty,
                false,
                &VersionPurgeStatusType::Empty
            ),
            None
        );
    }

    #[test]
    fn delete_replication_object_opts_marks_replica_deletes() {
        let version_id = Uuid::new_v4();
        let user_defined = HashMap::from([(SSEC_ALGORITHM_HEADER.to_string(), "AES256".to_string())]);
        let dobj = ObjectToDelete {
            object_name: "obj".to_string(),
            version_id: Some(version_id),
            ..Default::default()
        };
        let source = ReplicationDeleteSource {
            user_defined: &user_defined,
            user_tags: "env=prod",
            delete_marker: true,
            replication_status: ReplicationStatusType::Replica,
        };

        let opts = delete_replication_object_opts(&dobj, &source);

        assert!(opts.replica);
        assert!(opts.ssec);
        assert_eq!(opts.user_tags, "env=prod");
        assert_eq!(opts.version_id, Some(version_id));
        assert_eq!(opts.name, "obj");
        assert_eq!(opts.op_type, ReplicationType::Delete);
    }

    fn delete_replication_rule(arn: &str, replica_modifications: bool) -> ReplicationRule {
        ReplicationRule {
            delete_marker_replication: Some(DeleteMarkerReplication {
                status: Some(DeleteMarkerReplicationStatus::from_static(DeleteMarkerReplicationStatus::ENABLED)),
            }),
            delete_replication: None,
            destination: Destination {
                bucket: arn.to_string(),
                ..Default::default()
            },
            existing_object_replication: Some(ExistingObjectReplication {
                status: ExistingObjectReplicationStatus::from_static(ExistingObjectReplicationStatus::ENABLED),
            }),
            filter: None,
            id: Some("rule-1".to_string()),
            prefix: Some("test/".to_string()),
            priority: Some(1),
            source_selection_criteria: replica_modifications.then_some(SourceSelectionCriteria {
                replica_modifications: Some(ReplicaModifications {
                    status: ReplicaModificationsStatus::from_static(ReplicaModificationsStatus::ENABLED),
                }),
                sse_kms_encrypted_objects: None,
            }),
            status: ReplicationRuleStatus::from_static(ReplicationRuleStatus::ENABLED),
        }
    }

    #[test]
    fn delete_replication_state_tracks_downstream_delete_marker_targets() {
        let arn = "arn:aws:s3:::target-bucket";
        let config = ReplicationConfiguration {
            role: arn.to_string(),
            rules: vec![delete_replication_rule(arn, true)],
        };
        let source = ReplicationDeleteStateSource {
            name: "test/object.txt".to_string(),
            user_tags: String::new(),
            version_id: None,
            delete_marker: true,
            replica: true,
        };

        let state = delete_replication_state_from_config(&config, &source)
            .expect("replica delete marker should be forwarded to downstream targets");
        let pending = format!("{arn}=PENDING;");

        assert_eq!(state.replication_status_internal.as_deref(), Some(pending.as_str()));
        assert_eq!(state.replicate_decision_str, format!("{arn}=true;false;{arn};"));
        assert!(state.targets.contains_key(arn));
    }

    #[test]
    fn delete_replication_state_skips_replica_delete_without_replica_modifications() {
        let arn = "arn:aws:s3:::target-bucket";
        let config = ReplicationConfiguration {
            role: arn.to_string(),
            rules: vec![delete_replication_rule(arn, false)],
        };
        let source = ReplicationDeleteStateSource {
            name: "test/object.txt".to_string(),
            user_tags: String::new(),
            version_id: None,
            delete_marker: true,
            replica: true,
        };

        assert!(delete_replication_state_from_config(&config, &source).is_none());
    }

    #[test]
    fn delete_replication_state_uses_delete_switch_for_marker_version_purges() {
        let arn = "arn:aws:s3:::target-bucket";
        let mut rule = delete_replication_rule(arn, false);
        rule.delete_replication = Some(DeleteReplication {
            status: DeleteReplicationStatus::from_static(DeleteReplicationStatus::DISABLED),
        });
        let mut config = ReplicationConfiguration {
            role: arn.to_string(),
            rules: vec![rule],
        };
        let source = ReplicationDeleteStateSource {
            name: "test/object.txt".to_string(),
            user_tags: String::new(),
            version_id: Some(Uuid::new_v4()),
            delete_marker: true,
            replica: false,
        };

        assert!(
            delete_replication_state_from_config(&config, &source).is_none(),
            "delete-marker version purge must not use the enabled marker-creation switch"
        );

        config.rules[0].delete_replication = Some(DeleteReplication {
            status: DeleteReplicationStatus::from_static(DeleteReplicationStatus::ENABLED),
        });
        let state = delete_replication_state_from_config(&config, &source)
            .expect("delete-marker version purge should honor the enabled permanent-delete switch");
        let pending = format!("{arn}=PENDING;");

        assert_eq!(state.version_purge_status_internal.as_deref(), Some(pending.as_str()));
        assert_eq!(state.replicate_decision_str, format!("{arn}=true;false;{arn};"));
        assert!(state.purge_targets.contains_key(arn));
    }

    #[test]
    fn delete_replication_schedule_skips_replica_requests() {
        assert!(!should_schedule_delete_replication(ReplicationDeleteScheduleInput {
            replication_request: true,
            version_id_requested: true,
            source_delete_marker: true,
            source_replication_status: &ReplicationStatusType::Completed,
            source_version_purge_status: &VersionPurgeStatusType::Empty,
            deleted_delete_marker_version: true,
        }));
    }

    #[test]
    fn delete_replication_schedule_requires_current_admission_for_version_purges() {
        assert!(!should_schedule_delete_replication(ReplicationDeleteScheduleInput {
            replication_request: false,
            version_id_requested: true,
            source_delete_marker: true,
            source_replication_status: &ReplicationStatusType::Completed,
            source_version_purge_status: &VersionPurgeStatusType::Empty,
            deleted_delete_marker_version: true,
        }));
        assert!(should_schedule_delete_replication(ReplicationDeleteScheduleInput {
            replication_request: false,
            version_id_requested: true,
            source_delete_marker: false,
            source_replication_status: &ReplicationStatusType::Empty,
            source_version_purge_status: &VersionPurgeStatusType::Pending,
            deleted_delete_marker_version: true,
        }));
        assert!(should_schedule_delete_replication(ReplicationDeleteScheduleInput {
            replication_request: false,
            version_id_requested: true,
            source_delete_marker: false,
            source_replication_status: &ReplicationStatusType::Empty,
            source_version_purge_status: &VersionPurgeStatusType::Pending,
            deleted_delete_marker_version: false,
        }));
        assert!(should_schedule_delete_replication(ReplicationDeleteScheduleInput {
            replication_request: false,
            version_id_requested: false,
            source_delete_marker: false,
            source_replication_status: &ReplicationStatusType::Empty,
            source_version_purge_status: &VersionPurgeStatusType::Pending,
            deleted_delete_marker_version: false,
        }));
    }

    #[test]
    fn delete_replication_version_id_splits_marker_creation_and_purge() {
        let version_id = Uuid::new_v4();

        assert_eq!(delete_replication_version_id(true, Some(version_id), false), None);
        assert_eq!(delete_replication_version_id(true, Some(version_id), true), Some(version_id));
    }

    #[test]
    fn delete_replication_source_selection_prefers_existing_marker_source_only_for_replica_requests() {
        assert!(should_use_existing_delete_replication_source(true, true, true));
        assert!(!should_use_existing_delete_replication_source(false, true, true));
        assert!(!should_use_existing_delete_replication_source(true, false, true));
        assert!(!should_use_existing_delete_replication_source(true, true, false));
    }

    #[test]
    fn existing_delete_replication_info_is_limited_to_version_delete_requests() {
        assert!(should_use_existing_delete_replication_info(true, false));
        assert!(!should_use_existing_delete_replication_info(true, true));
        assert!(!should_use_existing_delete_replication_info(false, false));
    }

    /// P1-20 truth-table pin (rustfs/backlog#1675): without any reset in play
    /// (no per-target reset header on the object, empty reset id on the
    /// target) the existing-object resync decision compensates exactly the
    /// never-replicated objects — Empty replicates, any recorded status does
    /// not.
    #[test]
    fn resync_target_without_reset_replicates_only_empty_status() {
        let user_defined = HashMap::new();
        let object = ReplicationResyncTargetObject {
            mod_time: Some(OffsetDateTime::UNIX_EPOCH + Duration::seconds(10)),
            user_defined: &user_defined,
        };

        for (status, expected) in [
            (ReplicationStatusType::Empty, true),
            (ReplicationStatusType::Completed, false),
            // "COMPLETE" on disk parses to this legacy variant, so objects
            // written by older versions reach the decision through it.
            (ReplicationStatusType::CompletedLegacy, false),
            (ReplicationStatusType::Pending, false),
            (ReplicationStatusType::Failed, false),
            (ReplicationStatusType::Replica, false),
        ] {
            let label = format!("{status:?}");
            let decision = resync_target_for_object(&object, "arn:target", "", None, status);
            assert_eq!(
                decision.replicate, expected,
                "existing-object resync without a reset must replicate only never-replicated objects (status {label})"
            );
        }
    }

    #[test]
    fn resync_target_includes_object_at_reset_before_boundary() {
        let reset_before = OffsetDateTime::UNIX_EPOCH + Duration::seconds(30);
        let user_defined = HashMap::new();
        let object = ReplicationResyncTargetObject {
            mod_time: Some(reset_before),
            user_defined: &user_defined,
        };

        let decision =
            resync_target_for_object(&object, "arn:target", "reset-1", Some(reset_before), ReplicationStatusType::Completed);

        assert!(decision.replicate);
    }

    #[test]
    fn resync_target_replicates_when_reset_id_changes() {
        let reset_before = OffsetDateTime::UNIX_EPOCH + Duration::seconds(30);
        let user_defined = HashMap::from([(target_reset_header("arn:target"), "1970-01-01T00:00:20Z;old-reset".to_string())]);
        let object = ReplicationResyncTargetObject {
            mod_time: Some(OffsetDateTime::UNIX_EPOCH + Duration::seconds(10)),
            user_defined: &user_defined,
        };

        let decision =
            resync_target_for_object(&object, "arn:target", "new-reset", Some(reset_before), ReplicationStatusType::Completed);

        assert!(decision.replicate);
    }

    #[test]
    fn resync_target_skips_completed_object_for_same_reset_id() {
        let reset_before = OffsetDateTime::UNIX_EPOCH + Duration::seconds(30);
        let user_defined = HashMap::from([(target_reset_header("arn:target"), "1970-01-01T00:00:20Z;same-reset".to_string())]);
        let object = ReplicationResyncTargetObject {
            mod_time: Some(OffsetDateTime::UNIX_EPOCH + Duration::seconds(10)),
            user_defined: &user_defined,
        };

        let decision =
            resync_target_for_object(&object, "arn:target", "same-reset", Some(reset_before), ReplicationStatusType::Completed);

        assert!(!decision.replicate);
    }
}
