// 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 s3s::dto::ReplicationConfiguration;

use super::replication_config_boundary::ReplicationConfigurationExt as _;
use super::replication_filemeta_boundary::{
    REPLICATE_INCOMING_DELETE, ReplicateDecision, ReplicationState, version_purge_statuses_map,
};
use super::replication_object_config::{ReplicationConfig, check_replicate_delete};
use super::replication_queue_boundary::DeletedObjectReplicationInfo;
use super::replication_storage_boundary::{
    DeletedObject, ObjectInfo, ObjectOptions, ObjectToDelete, deleted_object_for_replication,
};
#[cfg(test)]
use std::sync::Mutex;

#[allow(
    dead_code,
    reason = "declared boundary surface for the ECStore replication split plan; no caller in this port (backlog#1823)"
)]
pub(crate) type ReplicationLifecycleConfig = ReplicationConfig;

pub(crate) struct ReplicationLifecycleBridge;

#[cfg(test)]
static SCHEDULED_DELETE_OBJECTS: Mutex<Vec<DeletedObject>> = Mutex::new(Vec::new());

impl ReplicationLifecycleBridge {
    #[allow(
        dead_code,
        reason = "declared boundary surface for the ECStore replication split plan; no caller in this port (backlog#1823)"
    )]
    pub(crate) fn new_config(config: ReplicationConfiguration) -> ReplicationLifecycleConfig {
        ReplicationConfig::new(Some(config), None)
    }

    #[allow(
        dead_code,
        reason = "declared boundary surface for the ECStore replication split plan; no caller in this port (backlog#1823)"
    )]
    pub(crate) fn has_pending_version_purge(
        config: &ReplicationLifecycleConfig,
        object_name: &str,
        version_purge_pending: bool,
    ) -> bool {
        version_purge_pending
            && config
                .config
                .as_ref()
                .is_some_and(|config| config.has_active_rules(object_name, true))
    }

    #[allow(
        dead_code,
        reason = "declared boundary surface for the ECStore replication split plan; no caller in this port (backlog#1823)"
    )]
    pub(crate) async fn check_delete_replication(
        bucket: &str,
        object: &ObjectToDelete,
        source: &ObjectInfo,
        opts: &ObjectOptions,
    ) -> ReplicateDecision {
        check_replicate_delete(bucket, object, source, opts, None).await
    }

    #[allow(
        dead_code,
        reason = "declared boundary surface for the ECStore replication split plan; no caller in this port (backlog#1823)"
    )]
    pub(crate) fn version_delete_replication_state(decision: &ReplicateDecision) -> ReplicationState {
        let pending_status = decision.pending_status();
        ReplicationState {
            replicate_decision_str: decision.to_string(),
            version_purge_status_internal: pending_status.clone(),
            purge_targets: version_purge_statuses_map(pending_status.as_deref().unwrap_or_default()),
            ..Default::default()
        }
    }

    pub(crate) async fn schedule_delete(bucket: String, delete_object: DeletedObject) {
        #[cfg(test)]
        {
            SCHEDULED_DELETE_OBJECTS
                .lock()
                .expect("scheduled delete test hook lock should not poison")
                .push(delete_object.clone());
        }
        super::replication_pool::schedule_replication_delete(DeletedObjectReplicationInfo {
            delete_object: deleted_object_for_replication(delete_object),
            bucket,
            event_type: REPLICATE_INCOMING_DELETE.to_string(),
            ..Default::default()
        })
        .await;
    }

    #[cfg(test)]
    pub(crate) fn take_scheduled_deletes_for_test() -> Vec<DeletedObject> {
        std::mem::take(
            &mut *SCHEDULED_DELETE_OBJECTS
                .lock()
                .expect("scheduled delete test hook lock should not poison"),
        )
    }
}

#[cfg(test)]
mod tests {
    use s3s::dto::{Destination, ReplicationRule, ReplicationRuleStatus};

    use super::super::replication_filemeta_boundary::{ReplicateTargetDecision, VersionPurgeStatusType};
    use super::*;

    fn replication_rule() -> ReplicationRule {
        ReplicationRule {
            delete_marker_replication: None,
            delete_replication: None,
            destination: Destination {
                bucket: "arn:aws:s3:::target-bucket".to_string(),
                ..Default::default()
            },
            existing_object_replication: None,
            filter: None,
            id: Some("rule".to_string()),
            prefix: Some(String::new()),
            priority: Some(1),
            source_selection_criteria: None,
            status: ReplicationRuleStatus::from_static(ReplicationRuleStatus::ENABLED),
        }
    }

    #[test]
    fn has_pending_version_purge_preserves_replication_active_rule_behavior() {
        let config = ReplicationLifecycleBridge::new_config(ReplicationConfiguration {
            role: String::new(),
            rules: vec![replication_rule()],
        });

        assert!(ReplicationLifecycleBridge::has_pending_version_purge(&config, "logs/object", true));
        assert!(!ReplicationLifecycleBridge::has_pending_version_purge(&config, "logs/object", false));
    }

    #[test]
    fn version_delete_replication_state_tracks_pending_purge_targets() {
        let target = ReplicateTargetDecision::new("arn:aws:s3:::target".to_string(), true, false);
        let mut decision = ReplicateDecision::new();
        decision.set(target);

        let state = ReplicationLifecycleBridge::version_delete_replication_state(&decision);

        assert_eq!(state.version_purge_status_internal.as_deref(), Some("arn:aws:s3:::target=PENDING;"));
        assert!(state.purge_targets.contains_key("arn:aws:s3:::target"));
        assert_eq!(state.purge_targets["arn:aws:s3:::target"], VersionPurgeStatusType::Pending);
    }
}
