use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReplicatedRepairTask {
    pub id: String,
    pub key: String,
    pub shard_index: u32,
    pub reason: String,
    pub enqueued_at_millis: u64,
    #[serde(default)]
    pub attempt_count: u32,
    #[serde(default)]
    pub last_error: Option<String>,
    #[serde(default)]
    pub next_attempt_after_millis: u64,
}

impl ReplicatedRepairTask {
    pub fn new(key: impl Into<String>, shard_index: u32, reason: impl Into<String>) -> Self {
        let key = key.into();
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as u64;
        Self {
            id: format!("replicated::{key}::{shard_index}"),
            key,
            shard_index,
            reason: reason.into(),
            enqueued_at_millis: now,
            attempt_count: 0,
            last_error: None,
            next_attempt_after_millis: now,
        }
    }

    pub fn schedule_retry(&mut self, error: impl Into<String>, backoff_millis: u64) {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as u64;
        self.attempt_count = self.attempt_count.saturating_add(1);
        self.last_error = Some(error.into());
        self.next_attempt_after_millis = now.saturating_add(backoff_millis);
    }

    pub fn is_due(&self, now_millis: u64) -> bool {
        now_millis >= self.next_attempt_after_millis
    }
}

#[async_trait]
pub trait RepairQueue: Send + Sync {
    async fn enqueue_repair(&self, task: ReplicatedRepairTask);
}

