use serde::{Deserialize, Serialize};
use std::fmt;

pub const PROJECT_KEY_PREFIX: &str = "email-unlimit";

pub fn namespaced_key(key: &str) -> String {
    if key == PROJECT_KEY_PREFIX
        || key
            .strip_prefix(PROJECT_KEY_PREFIX)
            .is_some_and(|suffix| suffix.starts_with(':'))
    {
        key.to_owned()
    } else {
        format!("{PROJECT_KEY_PREFIX}:{key}")
    }
}

/// 业务类型枚举
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum BusinessType {
    #[serde(rename = "auth")]
    Auth,
    #[serde(rename = "user")]
    User,
    #[serde(rename = "cache")]
    Cache,
    #[serde(rename = "session")]
    Session,
    #[serde(rename = "rate_limit")]
    RateLimit,
    #[serde(rename = "smtp")]
    Smtp,
    #[serde(rename = "greylist")]
    Greylist,
    #[serde(rename = "blacklist")]
    Blacklist,
    #[serde(rename = "abuse")]
    Abuse,
    #[serde(rename = "mailbox")]
    Mailbox,
}

impl BusinessType {
    pub fn prefix(self) -> &'static str {
        match self {
            BusinessType::Auth => "auth",
            BusinessType::User => "user",
            BusinessType::Cache => "cache",
            BusinessType::Session => "session",
            BusinessType::RateLimit => "rate_limit",
            BusinessType::Smtp => "smtp",
            BusinessType::Greylist => "greylist",
            BusinessType::Blacklist => "blacklist",
            BusinessType::Abuse => "abuse",
            BusinessType::Mailbox => "mailbox",
        }
    }
}

/// Redis 键构建器
#[derive(Debug, Clone)]
pub struct RedisKey {
    business: BusinessType,
    identifiers: Vec<String>,
}

impl RedisKey {
    pub fn new(business: BusinessType) -> Self {
        Self {
            business,
            identifiers: Vec::new(),
        }
    }

    pub fn add_identifier(mut self, id: impl Into<String>) -> Self {
        self.identifiers.push(id.into());
        self
    }

    pub fn build(&self) -> String {
        let business_key = if self.identifiers.is_empty() {
            self.business.prefix().to_owned()
        } else {
            format!("{}:{}", self.business.prefix(), self.identifiers.join(":"))
        };
        namespaced_key(&business_key)
    }
}

impl fmt::Display for RedisKey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.build())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn prefixes_structured_keys_with_project_name() {
        let key = RedisKey::new(BusinessType::Auth)
            .add_identifier("refresh_token")
            .add_identifier("user-1");
        assert_eq!(key.build(), "email-unlimit:auth:refresh_token:user-1");
        assert_eq!(
            RedisKey::new(BusinessType::Cache).build(),
            "email-unlimit:cache"
        );
    }

    #[test]
    fn prefixes_raw_keys_once() {
        assert_eq!(
            namespaced_key("smtp:rl:ip:127.0.0.1"),
            "email-unlimit:smtp:rl:ip:127.0.0.1"
        );
        assert_eq!(
            namespaced_key("email-unlimit:mail:verification:queue"),
            "email-unlimit:mail:verification:queue"
        );
    }
}
