//! MQTT Topic Handling Module
//!
//! Provides comprehensive implementation for MQTT topic parsing, validation and wildcard pattern
//! matching following MQTT 3.1.1/5.0 specifications. Implements hierarchical topic structure
//! with support for:
//! - Normal levels (`sport/tennis`)
//! - System metadata levels (`$SYS/...`)
//! - Single-level (`+`) and multi-level (`#`) wildcards
//!
//! ## Core Components
//! 1. **Topic Structure**:
//!    - `Level` enum representing different topic segment types:
//!      ```rust
//!      pub enum Level {
//!          Normal(String),       // Standard topic segment
//!          Metadata(String),    // System metadata starting with $
//!          Blank,               // Empty segment (e.g., leading/trailing /)
//!          SingleWildcard,      // + wildcard
//!          MultiWildcard        // # wildcard
//!      }
//!      ```
//!    - `Topic` struct containing ordered collection of Levels
//!
//! 2. **Validation Rules**:
//!    - Metadata levels must start with `$` and cannot contain wildcards
//!    - Multi-level wildcard `#` must be last segment
//!    - Single-level wildcard `+` matches exactly one level
//!    - Prohibits invalid characters (`+`/`#`) in normal levels
//!
//! 3. **Pattern Matching**:
//!    - Implements MQTT wildcard matching semantics:
//!      ```rust,ignore
//!      topic!("sport/+/#").matches_str("sport/tennis/player1") // true
//!      ```
//!    - Supports system metadata isolation (wildcards don't match $ topics)
//!
//! ## Design Features
//! - **Type-Safe Parsing**:
//!   - Implements `FromStr` with detailed error reporting
//!   - Strict validation during parsing
//! - **Zero-Copy Operations**:
//!   - Uses string references for pattern matching
//! - **Extensible Formatting**:
//!   - Implements `Display` trait for human-readable output
//!   - Provides `WriteTopicExt` for efficient byte-level writing
//!
//! ## Usage Example
//! ```rust,ignore
//! let topic: Topic = "sport/+/player1/#".parse().unwrap();
//! assert!(topic.matches_str("sport/tennis/player1/stats"));
//! ```
//!
//! Implements MQTT spec requirements for:
//! - Topic structure validation (§4.7)
//! - Wildcard matching rules (§4.7.1-4.7.3)
//! - System topic isolation (§4.7.3)
//!
//! : MQTT topic structure fundamentals
//! : Wildcard matching semantics

use std::fmt::{self, Write};
use std::{io, ops, str::FromStr};

use serde::{Deserialize, Serialize};

fn is_metadata<T: AsRef<str>>(s: T) -> bool {
    s.as_ref().starts_with('$')
}

#[derive(Clone, Debug, PartialEq, thiserror::Error)]
pub enum TopicError {
    InvalidTopic(String),
    InvalidLevel(String),
}

impl fmt::Display for TopicError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TopicError::InvalidTopic(s) => {
                write!(f, "InvalidTopic({s})")
            }
            TopicError::InvalidLevel(s) => {
                write!(f, "InvalidLevel({s})")
            }
        }
    }
}

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Serialize, Deserialize)]
pub enum Level {
    Normal(String),
    Metadata(String), // $SYS
    Blank,
    SingleWildcard, // Single level wildcard +
    MultiWildcard,  // Multi-level wildcard #
}

impl Level {
    pub fn parse<T: AsRef<str>>(s: T) -> Result<Level, TopicError> {
        Level::from_str(s.as_ref())
    }

    pub fn normal<T: AsRef<str>>(s: T) -> Result<Level, TopicError> {
        if s.as_ref().contains(['+', '#']) {
            return Err(TopicError::InvalidLevel(format!(
                "invalid normal level `{}` contains +|#",
                s.as_ref()
            )));
        }

        if s.as_ref().starts_with('$') {
            return Err(TopicError::InvalidLevel(format!(
                "invalid normal level `{}` starts with $",
                s.as_ref()
            )));
        }

        Ok(Level::Normal(String::from(s.as_ref())))
    }

    pub fn metadata<T: AsRef<str>>(s: T) -> Result<Level, TopicError> {
        if s.as_ref().contains(['+', '#']) {
            return Err(TopicError::InvalidLevel(format!(
                "invalid metadata level `{}` contains +|#",
                s.as_ref()
            )));
        }

        if !s.as_ref().starts_with('$') {
            return Err(TopicError::InvalidLevel(format!(
                "invalid metadata level `{}` not starts with $",
                s.as_ref()
            )));
        }

        Ok(Level::Metadata(String::from(s.as_ref())))
    }

    #[inline]
    pub fn value(&self) -> Option<&str> {
        match *self {
            Level::Normal(ref s) | Level::Metadata(ref s) => Some(s),
            _ => None,
        }
    }

    #[inline]
    pub fn is_normal(&self) -> bool {
        matches!(*self, Level::Normal(_))
    }

    #[inline]
    pub fn is_metadata(&self) -> bool {
        matches!(*self, Level::Metadata(_))
    }

    #[inline]
    pub fn is_valid(&self) -> bool {
        match *self {
            Level::Normal(ref s) => !s.starts_with('$') && !s.contains(['+', '#']),
            Level::Metadata(ref s) => s.starts_with('$') && !s.contains(['+', '#']),
            _ => true,
        }
    }
}

macro_rules! matches {
    ($topic:expr, $levels:expr) => {{
        let mut lhs = $topic.0.iter();

        for rhs in $levels {
            match lhs.next() {
                Some(&Level::SingleWildcard) => {
                    if !rhs.match_level(&Level::SingleWildcard) {
                        break;
                    }
                }
                Some(&Level::MultiWildcard) => {
                    return rhs.match_level(&Level::MultiWildcard);
                }
                Some(level) if rhs.match_level(level) => continue,
                _ => return false,
            }
        }

        match lhs.next() {
            Some(&Level::MultiWildcard) => true,
            Some(_) => false,
            None => true,
        }
    }};
}

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Serialize, Deserialize)]
pub struct Topic(Vec<Level>);

impl Topic {
    #[inline]
    pub fn levels(&self) -> &Vec<Level> {
        &self.0
    }

    #[inline]
    pub fn is_valid(&self) -> bool {
        self.0
            .iter()
            .position(|level| !level.is_valid())
            .or_else(|| {
                self.0.iter().enumerate().position(|(pos, level)| match *level {
                    Level::MultiWildcard => pos != self.0.len() - 1,
                    Level::Metadata(_) => pos != 0,
                    _ => false,
                })
            })
            .is_none()
    }

    pub fn matches(&self, topic: &Topic) -> bool {
        matches!(self, &topic.0)
    }

    pub fn matches_str<S: AsRef<str> + ?Sized>(&self, topic: &S) -> bool {
        matches!(self, topic.as_ref().split('/'))
    }

    /// Convert the topic to a string, optionally ignoring the first `n` levels.
    ///
    /// # Example
    /// ```
    /// let topic: rmqtt::topic::Topic = "a/b/c/d".parse().unwrap();
    /// assert_eq!(topic.to_string_skip(2), "c/d");
    ///
    /// let topic: rmqtt::topic::Topic = "sport/tennis/player1/stats".parse().unwrap();
    /// assert_eq!(topic.to_string_skip(0), "sport/tennis/player1/stats");
    /// assert_eq!(topic.to_string_skip(2), "player1/stats");
    /// assert_eq!(topic.to_string_skip(4), "");
    /// assert_eq!(topic.to_string_skip(6), "");
    /// ```
    #[inline]
    pub fn to_string_skip(&self, skip: usize) -> String {
        self.0.iter().skip(skip).map(|lvl| lvl.to_string()).collect::<Vec<_>>().join("/")
    }
}

impl From<&[Level]> for Topic {
    fn from(s: &[Level]) -> Self {
        let mut v = vec![];

        v.extend_from_slice(s);

        Topic(v)
    }
}

impl From<Vec<Level>> for Topic {
    fn from(v: Vec<Level>) -> Self {
        Topic(v)
    }
}

impl From<Topic> for Vec<Level> {
    fn from(t: Topic) -> Self {
        t.0
    }
}

impl ops::Deref for Topic {
    type Target = Vec<Level>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl ops::DerefMut for Topic {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

#[macro_export]
macro_rules! topic {
    ($s:expr) => {
        $s.parse::<Topic>()
    };
}

pub(crate) trait MatchLevel {
    fn match_level(&self, level: &Level) -> bool;
}

impl MatchLevel for Level {
    fn match_level(&self, level: &Level) -> bool {
        match *level {
            Level::Normal(ref lhs) => {
                if let Level::Normal(ref rhs) = *self {
                    lhs == rhs
                } else {
                    false
                }
            }
            Level::Metadata(ref lhs) => {
                if let Level::Metadata(ref rhs) = *self {
                    lhs == rhs
                } else {
                    false
                }
            }
            Level::Blank => true,
            Level::SingleWildcard | Level::MultiWildcard => !self.is_metadata(),
        }
    }
}

impl<T: AsRef<str>> MatchLevel for T {
    fn match_level(&self, level: &Level) -> bool {
        match *level {
            Level::Normal(ref lhs) => !is_metadata(self) && lhs == self.as_ref(),
            Level::Metadata(ref lhs) => is_metadata(self) && lhs == self.as_ref(),
            Level::Blank => self.as_ref().is_empty(),
            Level::SingleWildcard | Level::MultiWildcard => !is_metadata(self),
        }
    }
}

impl FromStr for Level {
    type Err = TopicError;

    #[inline]
    fn from_str(s: &str) -> Result<Self, TopicError> {
        match s {
            "+" => Ok(Level::SingleWildcard),
            "#" => Ok(Level::MultiWildcard),
            "" => Ok(Level::Blank),
            _ => {
                if s.contains(['+', '#']) {
                    Err(TopicError::InvalidLevel(format!("invalid level `{s}` contains +|#")))
                } else if is_metadata(s) {
                    Ok(Level::Metadata(String::from(s)))
                } else {
                    Ok(Level::Normal(String::from(s)))
                }
            }
        }
    }
}

impl FromStr for Topic {
    type Err = TopicError;

    #[inline]
    fn from_str(s: &str) -> Result<Self, TopicError> {
        s.split('/').map(Level::from_str).collect::<Result<Vec<_>, TopicError>>().map(Topic).and_then(
            |topic| {
                if topic.is_valid() {
                    Ok(topic)
                } else {
                    Err(TopicError::InvalidTopic(format!("invalid topic `{s}`")))
                }
            },
        )
    }
}

impl fmt::Display for Level {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Level::Normal(ref s) | Level::Metadata(ref s) => f.write_str(s.as_str()),
            Level::Blank => Ok(()),
            Level::SingleWildcard => f.write_char('+'),
            Level::MultiWildcard => f.write_char('#'),
        }
    }
}

impl fmt::Display for Topic {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut first = true;

        for level in &self.0 {
            if first {
                first = false;
            } else {
                f.write_char('/')?;
            }

            level.fmt(f)?;
        }

        Ok(())
    }
}

#[allow(dead_code)]
pub(crate) trait WriteTopicExt: io::Write {
    fn write_level(&mut self, level: &Level) -> io::Result<usize> {
        match *level {
            Level::Normal(ref s) | Level::Metadata(ref s) => self.write(s.as_str().as_bytes()),
            Level::Blank => Ok(0),
            Level::SingleWildcard => self.write(b"+"),
            Level::MultiWildcard => self.write(b"#"),
        }
    }

    fn write_topic(&mut self, topic: &Topic) -> io::Result<usize> {
        let mut n = 0;
        let mut first = true;

        for level in topic.levels() {
            if first {
                first = false;
            } else {
                n += self.write(b"/")?;
            }

            n += self.write_level(level)?;
        }

        Ok(n)
    }
}

impl<W: io::Write + ?Sized> WriteTopicExt for W {}

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

    #[test]
    fn test_level() {
        assert!(Level::normal("sport").unwrap().is_normal());
        assert!(Level::metadata("$SYS").unwrap().is_metadata());

        assert_eq!(Level::normal("sport").unwrap().value(), Some("sport"));
        assert_eq!(Level::metadata("$SYS").unwrap().value(), Some("$SYS"));

        assert_eq!(Level::normal("sport").unwrap(), "sport".parse().expect(""));
        assert_eq!(Level::metadata("$SYS").unwrap(), "$SYS".parse().expect(""));

        assert!(Level::Normal(String::from("sport")).is_valid());
        assert!(Level::Metadata(String::from("$SYS")).is_valid());

        assert!(!Level::Normal(String::from("$sport")).is_valid());
        assert!(!Level::Metadata(String::from("SYS")).is_valid());

        assert!(!Level::Normal(String::from("sport#")).is_valid());
        assert!(!Level::Metadata(String::from("SYS+")).is_valid());
    }

    #[test]
    fn test_valid_topic() {
        assert!(Topic(vec![
            Level::normal("sport").unwrap(),
            Level::normal("tennis").unwrap(),
            Level::normal("player1").unwrap()
        ])
        .is_valid());

        assert!(Topic(vec![
            Level::normal("sport").unwrap(),
            Level::normal("tennis").unwrap(),
            Level::MultiWildcard
        ])
        .is_valid());
        assert!(Topic(vec![
            Level::metadata("$SYS").unwrap(),
            Level::normal("tennis").unwrap(),
            Level::MultiWildcard
        ])
        .is_valid());

        assert!(Topic(vec![
            Level::normal("sport").unwrap(),
            Level::SingleWildcard,
            Level::normal("player1").unwrap()
        ])
        .is_valid());

        assert!(!Topic(vec![
            Level::normal("sport").unwrap(),
            Level::MultiWildcard,
            Level::normal("player1").unwrap()
        ])
        .is_valid());
        assert!(!Topic(vec![
            Level::normal("sport").unwrap(),
            Level::metadata("$SYS").unwrap(),
            Level::normal("player1").unwrap()
        ])
        .is_valid());
    }

    #[test]
    fn test_parse_topic() {
        assert!(topic!("sport/tennis/player1").unwrap().matches(&Topic::from(vec![
            Level::normal("sport").unwrap(),
            Level::normal("tennis").unwrap(),
            Level::normal("player1").unwrap()
        ])));

        assert!(topic!("").unwrap().matches(&Topic(vec![Level::Blank])));
        assert!(topic!("/finance")
            .unwrap()
            .matches(&Topic::from(vec![Level::Blank, Level::normal("finance").unwrap()])));

        assert!(topic!("$SYS").unwrap().matches(&Topic::from(vec![Level::metadata("$SYS").unwrap()])));
        assert!("sport/$SYS".parse::<Topic>().is_err());
    }

    #[test]
    fn test_multi_wildcard_topic() {
        assert!(topic!("sport/tennis/#").unwrap().matches(&Topic::from(vec![
            Level::normal("sport").unwrap(),
            Level::normal("tennis").unwrap(),
            Level::MultiWildcard
        ])));

        assert!(topic!("#").unwrap().matches(&Topic::from(vec![Level::MultiWildcard])));
        assert!("sport/tennis#".parse::<Topic>().is_err());
        assert!("sport/tennis/#/ranking".parse::<Topic>().is_err());
    }

    #[test]
    fn test_single_wildcard_topic() {
        assert!(topic!("+").unwrap().matches(&Topic::from(vec![Level::SingleWildcard])));

        assert!(topic!("+/tennis/#").unwrap().matches(&Topic::from(vec![
            Level::SingleWildcard,
            Level::normal("tennis").unwrap(),
            Level::MultiWildcard
        ])));

        assert!(topic!("sport/+/player1").unwrap().matches(&Topic::from(vec![
            Level::normal("sport").unwrap(),
            Level::SingleWildcard,
            Level::normal("player1").unwrap()
        ])));

        assert!("sport+".parse::<Topic>().is_err());
    }

    #[test]
    fn test_write_topic() {
        let mut v = vec![];
        let t = vec![Level::SingleWildcard, Level::normal("tennis").unwrap(), Level::MultiWildcard].into();

        assert_eq!(v.write_topic(&t).expect(""), 10);
        assert_eq!(v, b"+/tennis/#");

        assert_eq!(format!("{t}"), "+/tennis/#");
        assert_eq!(t.to_string(), "+/tennis/#");
    }

    #[test]
    fn test_matches() {
        assert!("test".match_level(&Level::normal("test").unwrap()));
        assert!("$SYS".match_level(&Level::metadata("$SYS").unwrap()));

        let t: Topic = "sport/tennis/player1/#".parse().expect("");

        assert!(t.matches_str("sport/tennis/player1"));
        assert!(t.matches_str("sport/tennis/player1/ranking"));
        assert!(t.matches_str("sport/tennis/player1/score/wimbledon"));

        assert!(Topic::from_str("sport/#").expect("").matches_str("sport"));

        let t: Topic = "sport/tennis/+".parse().expect("");

        assert!(t.matches_str("sport/tennis/player1"));
        assert!(t.matches_str("sport/tennis/player2"));
        assert!(!t.matches_str("sport/tennis/player1/ranking"));

        let t: Topic = "sport/+".parse().expect("");

        assert!(!t.matches_str("sport"));
        assert!(t.matches_str("sport/"));

        assert!(Topic::from_str("+/+").expect("").matches_str("/finance"));
        assert!(Topic::from_str("/+").expect("").matches_str("/finance"));
        assert!(!Topic::from_str("+").expect("").matches_str("/finance"));

        assert!(!Topic::from_str("#").expect("").matches_str("$SYS"));
        assert!(!Topic::from_str("+/monitor/Clients").expect("").matches_str("$SYS/monitor/Clients"));
        assert!(Topic::from_str("$SYS/#").expect("").matches_str("$SYS/"));
        assert!(Topic::from_str("$SYS/monitor/+").expect("").matches_str("$SYS/monitor/Clients"));
    }
}
