use common::character::MAX_NAME_LENGTH;
use std::fmt::{self, Display};

#[derive(Debug, Default)]
pub struct AliasValidator {
    banned_substrings: Vec<String>,
}

impl AliasValidator {
    pub fn new(banned_substrings: Vec<String>) -> Self {
        let banned_substrings = banned_substrings
            .iter()
            .map(|string| string.to_lowercase())
            .collect();

        AliasValidator { banned_substrings }
    }

    pub fn validate(&self, alias: &str) -> Result<(), ValidatorError> {
        if alias.len() > MAX_NAME_LENGTH {
            return Err(ValidatorError::TooLong(alias.to_owned(), alias.len()));
        }

        let lowercase_alias = alias.to_lowercase();

        for banned_word in self.banned_substrings.iter() {
            if lowercase_alias.contains(banned_word) {
                return Err(ValidatorError::Forbidden(
                    alias.to_owned(),
                    banned_word.to_owned(),
                ));
            }
        }
        Ok(())
    }
}

#[derive(Debug, PartialEq)]
pub enum ValidatorError {
    Forbidden(String, String),
    TooLong(String, usize),
}

impl Display for ValidatorError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Forbidden(name, _) => write!(
                formatter,
                "Character name \"{}\" contains a banned word",
                name
            ),
            Self::TooLong(name, _) => write!(formatter, "Character name \"{}\" too long", name),
        }
    }
}

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

    #[test]
    fn multiple_matches() {
        let banned_substrings = vec!["bad".to_owned(), "worse".to_owned()];
        let validator = AliasValidator::new(banned_substrings);

        let bad_alias = "BadplayerMcWorseFace";
        let result = validator.validate(bad_alias);

        assert_eq!(
            result,
            Err(ValidatorError::Forbidden(
                bad_alias.to_owned(),
                "bad".to_owned()
            ))
        );
    }

    #[test]
    fn single_lowercase_match() {
        let banned_substrings = vec!["blue".to_owned()];
        let validator = AliasValidator::new(banned_substrings);

        let bad_alias = "blueName";
        let result = validator.validate(bad_alias);

        assert_eq!(
            result,
            Err(ValidatorError::Forbidden(
                bad_alias.to_owned(),
                "blue".to_owned()
            ))
        );
    }

    #[test]
    fn single_case_insensitive_match() {
        let banned_substrings = vec!["GrEEn".to_owned()];
        let validator = AliasValidator::new(banned_substrings);

        let bad_alias = "gReenName";
        let result = validator.validate(bad_alias);

        assert_eq!(
            result,
            Err(ValidatorError::Forbidden(
                bad_alias.to_owned(),
                "green".to_owned()
            ))
        );
    }

    #[test]
    fn mp_matches() {
        let banned_substrings = vec!["orange".to_owned()];
        let validator = AliasValidator::new(banned_substrings);

        let good_alias = "ReasonableName";
        let result = validator.validate(good_alias);

        assert_eq!(result, Ok(()));
    }

    #[test]
    fn too_long() {
        let banned_substrings = vec!["orange".to_owned()];
        let validator = AliasValidator::new(banned_substrings);

        let bad_alias = "Thisnameistoolong Muchtoolong MuchTooLongByFar";
        let result = validator.validate(bad_alias);

        assert_eq!(
            result,
            Err(ValidatorError::TooLong(
                bad_alias.to_owned(),
                bad_alias.chars().count()
            ))
        );
    }
}
