//
// Copyright (C) 2024 - 2025 Automated Design Corp. All Rights Reserved.
//

//! Topic-based message routing for module handlers.
//!
//! This module provides a `TopicRouter` that allows modules to register
//! handlers for specific topics or topic patterns. This reduces boilerplate
//! in module implementations by automating the dispatch logic.
//!
//! Topics use FQDN format: `domain.subtopic.path` (e.g., "ads.plc1.GM.stData").
//! The router matches against the full topic or can extract subtopics for routing.
//!
//! # Example
//!
//! ```ignore
//! use mechutil::ipc::{TopicRouter, RouteHandler, CommandMessage};
//!
//! struct MyModule {
//!     router: TopicRouter,
//!     holding_registers: Vec<u16>,
//! }
//!
//! impl MyModule {
//!     fn new() -> Self {
//!         let mut router = TopicRouter::new();
//!
//!         // Register handlers for different topics (matches subtopic portion)
//!         router.register("holding_registers", |msg, ctx| {
//!             // Handle holding register operations
//!             msg.into_response(serde_json::json!({"status": "ok"}))
//!         });
//!
//!         router.register("coils", |msg, ctx| {
//!             // Handle coil operations
//!             msg.into_response(serde_json::json!({"status": "ok"}))
//!         });
//!
//!         // Pattern matching with wildcards
//!         router.register_pattern("status.*", |msg, ctx| {
//!             // Handle any status topic
//!             msg.into_response(serde_json::json!({"status": "ok"}))
//!         });
//!
//!         Self {
//!             router,
//!             holding_registers: vec![0; 100],
//!         }
//!     }
//! }
//! ```

use std::collections::HashMap;
use std::sync::Arc;

use super::command_message::CommandMessage;

/// Context passed to route handlers.
#[derive(Debug, Clone, Default)]
pub struct RouteContext {
    /// The matched topic pattern (if any)
    pub matched_pattern: Option<String>,
    /// Captured groups from pattern matching
    pub captures: Vec<String>,
    /// Custom user data
    pub user_data: serde_json::Value,
}

impl RouteContext {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_pattern(pattern: &str) -> Self {
        Self {
            matched_pattern: Some(pattern.to_string()),
            ..Default::default()
        }
    }

    pub fn with_captures(mut self, captures: Vec<String>) -> Self {
        self.captures = captures;
        self
    }

    pub fn with_user_data(mut self, data: serde_json::Value) -> Self {
        self.user_data = data;
        self
    }
}

/// Result of route matching.
#[derive(Debug, Clone)]
pub struct RouteMatch {
    /// The matched route name/pattern
    pub route: String,
    /// Context with any captured values
    pub context: RouteContext,
}

/// Trait for route handler functions.
pub trait RouteHandler: Send + Sync {
    /// Handle a routed message.
    fn handle(&self, msg: CommandMessage, ctx: &RouteContext) -> CommandMessage;
}

/// Implement RouteHandler for closures.
impl<F> RouteHandler for F
where
    F: Fn(CommandMessage, &RouteContext) -> CommandMessage + Send + Sync,
{
    fn handle(&self, msg: CommandMessage, ctx: &RouteContext) -> CommandMessage {
        self(msg, ctx)
    }
}

/// A route entry with its handler.
struct Route {
    /// The topic or pattern
    pattern: String,
    /// Whether this is a wildcard pattern
    is_pattern: bool,
    /// The handler function
    handler: Arc<dyn RouteHandler>,
}

impl Route {
    fn new<H: RouteHandler + 'static>(pattern: &str, is_pattern: bool, handler: H) -> Self {
        Self {
            pattern: pattern.to_string(),
            is_pattern,
            handler: Arc::new(handler),
        }
    }

    /// Check if this route matches the given topic.
    fn matches(&self, topic: &str) -> Option<RouteContext> {
        if self.is_pattern {
            self.match_pattern(topic)
        } else if self.pattern == topic || self.pattern == "*" {
            Some(RouteContext::with_pattern(&self.pattern))
        } else {
            None
        }
    }

    /// Match a wildcard pattern against a topic.
    ///
    /// Supports:
    /// - `*` - matches any single segment
    /// - `**` - matches any number of segments
    /// - `prefix.*` - matches prefix.anything
    /// - `*.suffix` - matches anything.suffix
    fn match_pattern(&self, topic: &str) -> Option<RouteContext> {
        let pattern_parts: Vec<&str> = self.pattern.split('.').collect();
        let topic_parts: Vec<&str> = topic.split('.').collect();

        let mut captures = Vec::new();
        let mut pi = 0; // pattern index
        let mut ti = 0; // topic index

        while pi < pattern_parts.len() {
            let pattern_part = pattern_parts[pi];

            if pattern_part == "**" {
                // ** matches everything remaining
                let remaining: Vec<&str> = topic_parts[ti..].to_vec();
                captures.push(remaining.join("."));
                return Some(
                    RouteContext::with_pattern(&self.pattern).with_captures(captures),
                );
            } else if pattern_part == "*" {
                // * matches exactly one segment
                if ti >= topic_parts.len() {
                    return None;
                }
                captures.push(topic_parts[ti].to_string());
                pi += 1;
                ti += 1;
            } else {
                // Exact match required
                if ti >= topic_parts.len() || pattern_part != topic_parts[ti] {
                    return None;
                }
                pi += 1;
                ti += 1;
            }
        }

        // Both must be exhausted for a match
        if ti == topic_parts.len() {
            Some(RouteContext::with_pattern(&self.pattern).with_captures(captures))
        } else {
            None
        }
    }
}

/// Router for dispatching messages to handlers based on topic.
///
/// Topics use FQDN format (e.g., "domain.subtopic.path"). The router can match
/// against the full topic or just the subtopic portion (everything after the first dot).
pub struct TopicRouter {
    /// Exact match routes (fast lookup) - matches against subtopic
    exact_routes: HashMap<String, Arc<dyn RouteHandler>>,
    /// Pattern routes (checked in order) - can match full topic or subtopic
    pattern_routes: Vec<Route>,
    /// Default handler for unmatched topics
    default_handler: Option<Arc<dyn RouteHandler>>,
    /// Whether to match against subtopic only (true) or full topic (false)
    match_subtopic: bool,
}

impl TopicRouter {
    /// Create a new empty router that matches against subtopics.
    pub fn new() -> Self {
        Self {
            exact_routes: HashMap::new(),
            pattern_routes: Vec::new(),
            default_handler: None,
            match_subtopic: true,
        }
    }

    /// Create a new router that matches against full topics.
    pub fn new_full_topic() -> Self {
        Self {
            exact_routes: HashMap::new(),
            pattern_routes: Vec::new(),
            default_handler: None,
            match_subtopic: false,
        }
    }

    /// Register an exact match handler for a topic/subtopic.
    ///
    /// # Arguments
    /// * `topic` - The exact topic to match (e.g., "holding_registers")
    /// * `handler` - The handler function
    pub fn register<H: RouteHandler + 'static>(&mut self, topic: &str, handler: H) {
        self.exact_routes
            .insert(topic.to_string(), Arc::new(handler));
    }

    /// Register a pattern handler for topics matching a pattern.
    ///
    /// # Arguments
    /// * `pattern` - The pattern to match (supports `*` and `**` wildcards)
    /// * `handler` - The handler function
    ///
    /// # Pattern Syntax
    /// - `*` matches exactly one segment (e.g., `status.*` matches `status.ok`)
    /// - `**` matches any number of segments (e.g., `data.**` matches `data.sensors.temp`)
    pub fn register_pattern<H: RouteHandler + 'static>(&mut self, pattern: &str, handler: H) {
        self.pattern_routes
            .push(Route::new(pattern, true, handler));
    }

    /// Set the default handler for unmatched topics.
    pub fn set_default<H: RouteHandler + 'static>(&mut self, handler: H) {
        self.default_handler = Some(Arc::new(handler));
    }

    /// Get the routing key from a message (either full topic or subtopic).
    fn get_routing_key(&self, msg: &CommandMessage) -> String {
        if self.match_subtopic {
            let subtopic = msg.subtopic();
            if subtopic.is_empty() {
                msg.topic.clone()
            } else {
                subtopic
            }
        } else {
            msg.topic.clone()
        }
    }

    /// Route a message to the appropriate handler.
    ///
    /// The routing priority is:
    /// 1. Exact match routes
    /// 2. Pattern routes (in registration order)
    /// 3. Default handler
    /// 4. Returns error response if no handler found
    pub fn route(&self, msg: CommandMessage) -> CommandMessage {
        let routing_key = self.get_routing_key(&msg);

        // Try exact match first (fastest)
        if let Some(handler) = self.exact_routes.get(&routing_key) {
            let ctx = RouteContext::new();
            return handler.handle(msg, &ctx);
        }

        // Try pattern routes
        for route in &self.pattern_routes {
            if let Some(ctx) = route.matches(&routing_key) {
                return route.handler.handle(msg, &ctx);
            }
        }

        // Try default handler
        if let Some(handler) = &self.default_handler {
            let ctx = RouteContext::new();
            return handler.handle(msg, &ctx);
        }

        // No handler found - return error
        let error_msg = format!("No handler for topic: {}", routing_key);
        msg.into_error_response(&error_msg)
    }

    /// Check if a topic has a registered handler.
    pub fn has_handler(&self, topic: &str) -> bool {
        // For has_handler, we check the exact string given
        if self.exact_routes.contains_key(topic) {
            return true;
        }

        for route in &self.pattern_routes {
            if route.matches(topic).is_some() {
                return true;
            }
        }

        self.default_handler.is_some()
    }

    /// Get all registered exact topics.
    pub fn registered_topics(&self) -> Vec<String> {
        self.exact_routes.keys().cloned().collect()
    }

    /// Get all registered patterns.
    pub fn registered_patterns(&self) -> Vec<String> {
        self.pattern_routes.iter().map(|r| r.pattern.clone()).collect()
    }
}

impl Default for TopicRouter {
    fn default() -> Self {
        Self::new()
    }
}

/// Builder for TopicRouter with fluent API.
pub struct TopicRouterBuilder {
    router: TopicRouter,
}

impl TopicRouterBuilder {
    pub fn new() -> Self {
        Self {
            router: TopicRouter::new(),
        }
    }

    pub fn route<H: RouteHandler + 'static>(mut self, topic: &str, handler: H) -> Self {
        self.router.register(topic, handler);
        self
    }

    pub fn pattern<H: RouteHandler + 'static>(mut self, pattern: &str, handler: H) -> Self {
        self.router.register_pattern(pattern, handler);
        self
    }

    pub fn default<H: RouteHandler + 'static>(mut self, handler: H) -> Self {
        self.router.set_default(handler);
        self
    }

    pub fn build(self) -> TopicRouter {
        self.router
    }
}

impl Default for TopicRouterBuilder {
    fn default() -> Self {
        Self::new()
    }
}

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

    /// Create a test message with a full FQDN topic (domain.subtopic)
    fn make_msg(subtopic: &str) -> CommandMessage {
        // For testing with subtopic matching, we create a topic like "TEST.subtopic"
        CommandMessage::read(&format!("TEST.{}", subtopic))
    }

    #[test]
    fn test_exact_match() {
        let mut router = TopicRouter::new();

        router.register("test_topic", |msg: CommandMessage, _ctx: &RouteContext| {
            msg.into_response(serde_json::json!({"matched": "exact"}))
        });

        let msg = make_msg("test_topic");
        let result = router.route(msg);

        assert!(result.success);
        assert_eq!(result.data["matched"], "exact");
    }

    #[test]
    fn test_pattern_match_single_wildcard() {
        let mut router = TopicRouter::new();

        router.register_pattern("status.*", |msg: CommandMessage, ctx: &RouteContext| {
            msg.into_response(serde_json::json!({
                "matched": "pattern",
                "captures": ctx.captures
            }))
        });

        let msg = make_msg("status.ok");
        let result = router.route(msg);

        assert!(result.success);
        assert_eq!(result.data["matched"], "pattern");
        assert_eq!(result.data["captures"][0], "ok");

        // Should not match status.sub.topic
        let msg2 = make_msg("status.sub.topic");
        let result2 = router.route(msg2);
        assert!(!result2.success);
    }

    #[test]
    fn test_pattern_match_double_wildcard() {
        let mut router = TopicRouter::new();

        router.register_pattern("data.**", |msg: CommandMessage, ctx: &RouteContext| {
            msg.into_response(serde_json::json!({
                "matched": "double_wildcard",
                "captures": ctx.captures
            }))
        });

        // Should match data.sensors.temperature.value
        let msg = make_msg("data.sensors.temperature.value");
        let result = router.route(msg);

        assert!(result.success);
        assert_eq!(result.data["captures"][0], "sensors.temperature.value");
    }

    #[test]
    fn test_default_handler() {
        let mut router = TopicRouter::new();

        router.set_default(|msg: CommandMessage, _ctx: &RouteContext| {
            msg.into_response(serde_json::json!({"matched": "default"}))
        });

        let msg = make_msg("unknown_topic");
        let result = router.route(msg);

        assert!(result.success);
        assert_eq!(result.data["matched"], "default");
    }

    #[test]
    fn test_no_handler() {
        let router = TopicRouter::new();

        let msg = make_msg("unhandled");
        let result = router.route(msg);

        assert!(!result.success);
        assert!(result.error_message.contains("No handler"));
    }

    #[test]
    fn test_exact_takes_priority() {
        let mut router = TopicRouter::new();

        router.register_pattern("test.*", |msg: CommandMessage, _ctx: &RouteContext| {
            msg.into_response(serde_json::json!({"matched": "pattern"}))
        });

        router.register("test.specific", |msg: CommandMessage, _ctx: &RouteContext| {
            msg.into_response(serde_json::json!({"matched": "exact"}))
        });

        // Exact match should win
        let msg = make_msg("test.specific");
        let result = router.route(msg);
        assert_eq!(result.data["matched"], "exact");

        // Pattern should match other
        let msg2 = make_msg("test.other");
        let result2 = router.route(msg2);
        assert_eq!(result2.data["matched"], "pattern");
    }

    #[test]
    fn test_builder() {
        let router = TopicRouterBuilder::new()
            .route("exact", |msg: CommandMessage, _: &RouteContext| {
                msg.into_response(serde_json::Value::Null)
            })
            .pattern("pattern.*", |msg: CommandMessage, _: &RouteContext| {
                msg.into_response(serde_json::Value::Null)
            })
            .default(|msg: CommandMessage, _: &RouteContext| {
                msg.into_error_response("default handler")
            })
            .build();

        assert!(router.has_handler("exact"));
        assert!(router.has_handler("pattern.test"));
        assert!(router.has_handler("anything_else"));
    }

    #[test]
    fn test_full_topic_router() {
        // Test router that matches full topic instead of subtopic
        let mut router = TopicRouter::new_full_topic();

        router.register("domain.subtopic", |msg: CommandMessage, _ctx: &RouteContext| {
            msg.into_response(serde_json::json!({"matched": "full"}))
        });

        // Create a message with exact full topic
        let msg = CommandMessage::read("domain.subtopic");
        let result = router.route(msg);

        assert!(result.success);
        assert_eq!(result.data["matched"], "full");
    }
}
