use std::sync::Mutex;
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use tracing::debug;
use punch_types::FighterId;
use crate::ChannelPlatform;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RouteKey {
pub platform: String,
pub user_id: String,
}
impl RouteKey {
pub fn new(platform: &ChannelPlatform, user_id: &str) -> Self {
Self {
platform: platform.to_string(),
user_id: user_id.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct ChannelRouteConfig {
pub default_fighter: String,
pub platform: ChannelPlatform,
}
pub struct ChannelRouter {
direct_routes: DashMap<RouteKey, FighterId>,
user_defaults: DashMap<String, FighterId>,
channel_defaults: DashMap<String, String>,
system_default: Mutex<Option<FighterId>>,
fighter_cache: DashMap<String, FighterId>,
}
impl ChannelRouter {
pub fn new() -> Self {
Self {
direct_routes: DashMap::new(),
user_defaults: DashMap::new(),
channel_defaults: DashMap::new(),
system_default: Mutex::new(None),
fighter_cache: DashMap::new(),
}
}
pub fn set_system_default(&self, fighter_id: FighterId) {
*self.system_default.lock().unwrap() = Some(fighter_id);
}
pub fn set_channel_default(&self, platform: &ChannelPlatform, fighter_name: String) {
self.channel_defaults
.insert(platform.to_string(), fighter_name);
}
pub fn set_direct_route(
&self,
platform: &ChannelPlatform,
user_id: &str,
fighter_id: FighterId,
) {
let key = RouteKey::new(platform, user_id);
self.direct_routes.insert(key, fighter_id);
debug!(
platform = %platform,
user_id = %user_id,
fighter_id = %fighter_id,
"direct route set"
);
}
pub fn set_user_default(&self, user_id: &str, fighter_id: FighterId) {
self.user_defaults.insert(user_id.to_string(), fighter_id);
}
pub fn register_fighter(&self, name: String, id: FighterId) {
self.fighter_cache.insert(name, id);
}
pub fn resolve(&self, platform: &ChannelPlatform, user_id: &str) -> Option<FighterId> {
let key = RouteKey::new(platform, user_id);
if let Some(fighter_id) = self.direct_routes.get(&key) {
return Some(*fighter_id);
}
if let Some(fighter_id) = self.user_defaults.get(user_id) {
return Some(*fighter_id);
}
let platform_str = platform.to_string();
if let Some(fighter_name) = self.channel_defaults.get(&platform_str)
&& let Some(fighter_id) = self.fighter_cache.get(fighter_name.value())
{
return Some(*fighter_id);
}
*self.system_default.lock().unwrap()
}
pub fn channel_default_name(&self, platform: &ChannelPlatform) -> Option<String> {
self.channel_defaults
.get(&platform.to_string())
.map(|v| v.value().clone())
}
pub fn has_route(&self, platform: &ChannelPlatform, user_id: &str) -> bool {
let key = RouteKey::new(platform, user_id);
self.direct_routes.contains_key(&key) || self.user_defaults.contains_key(user_id)
}
pub fn list_channel_defaults(&self) -> Vec<(String, String)> {
self.channel_defaults
.iter()
.map(|entry| (entry.key().clone(), entry.value().clone()))
.collect()
}
pub fn stats(&self) -> RouterStats {
RouterStats {
direct_routes: self.direct_routes.len(),
user_defaults: self.user_defaults.len(),
channel_defaults: self.channel_defaults.len(),
registered_fighters: self.fighter_cache.len(),
}
}
}
impl Default for ChannelRouter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RouterStats {
pub direct_routes: usize,
pub user_defaults: usize,
pub channel_defaults: usize,
pub registered_fighters: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_routing_priority_direct() {
let router = ChannelRouter::new();
let direct_id = FighterId::new();
let default_id = FighterId::new();
router.set_system_default(default_id);
router.set_direct_route(&ChannelPlatform::Telegram, "user1", direct_id);
let resolved = router.resolve(&ChannelPlatform::Telegram, "user1");
assert_eq!(resolved, Some(direct_id));
let resolved = router.resolve(&ChannelPlatform::Telegram, "user2");
assert_eq!(resolved, Some(default_id));
}
#[test]
fn test_routing_priority_user_default() {
let router = ChannelRouter::new();
let user_fighter = FighterId::new();
let system_fighter = FighterId::new();
router.set_system_default(system_fighter);
router.set_user_default("alice", user_fighter);
let resolved = router.resolve(&ChannelPlatform::Discord, "alice");
assert_eq!(resolved, Some(user_fighter));
}
#[test]
fn test_routing_priority_channel_default() {
let router = ChannelRouter::new();
let channel_fighter = FighterId::new();
router.register_fighter("oracle".to_string(), channel_fighter);
router.set_channel_default(&ChannelPlatform::Telegram, "oracle".to_string());
let resolved = router.resolve(&ChannelPlatform::Telegram, "any_user");
assert_eq!(resolved, Some(channel_fighter));
let resolved = router.resolve(&ChannelPlatform::Discord, "any_user");
assert_eq!(resolved, None);
}
#[test]
fn test_no_route() {
let router = ChannelRouter::new();
let resolved = router.resolve(&ChannelPlatform::Telegram, "user1");
assert_eq!(resolved, None);
}
#[test]
fn test_same_user_different_platforms() {
let router = ChannelRouter::new();
let tg_fighter = FighterId::new();
let dc_fighter = FighterId::new();
router.set_direct_route(&ChannelPlatform::Telegram, "user1", tg_fighter);
router.set_direct_route(&ChannelPlatform::Discord, "user1", dc_fighter);
let tg = router.resolve(&ChannelPlatform::Telegram, "user1");
let dc = router.resolve(&ChannelPlatform::Discord, "user1");
assert_eq!(tg, Some(tg_fighter));
assert_eq!(dc, Some(dc_fighter));
assert_ne!(tg_fighter, dc_fighter);
}
#[test]
fn test_has_route() {
let router = ChannelRouter::new();
let fighter_id = FighterId::new();
assert!(!router.has_route(&ChannelPlatform::Telegram, "user1"));
router.set_direct_route(&ChannelPlatform::Telegram, "user1", fighter_id);
assert!(router.has_route(&ChannelPlatform::Telegram, "user1"));
}
#[test]
fn test_stats() {
let router = ChannelRouter::new();
let id = FighterId::new();
router.set_direct_route(&ChannelPlatform::Telegram, "u1", id);
router.set_user_default("u2", id);
router.set_channel_default(&ChannelPlatform::Discord, "bot".to_string());
router.register_fighter("bot".to_string(), id);
let stats = router.stats();
assert_eq!(stats.direct_routes, 1);
assert_eq!(stats.user_defaults, 1);
assert_eq!(stats.channel_defaults, 1);
assert_eq!(stats.registered_fighters, 1);
}
}