use utils::{
    hash::{Hash, Hashable},
    serialize::{self, Serialize, SetOfSerializedObjects},
    time::time_millis,
};

use crate::{INVALID_MESSAGE_IDENT, MAX_MESSAGE_LENGTH, MAX_USERNAME_LENGTH};

#[derive(Debug, Clone)]
pub struct Message {
    pub sender: String,
    pub content: String,
    pub channel: String,
    pub time: u128,
}

impl Message {
    pub fn new(sender: &str, content: &str, channel: &str) -> Self {
        Message {
            sender: sender.to_string(),
            content: content.to_string(),
            channel: channel.to_string(),
            time: time_millis(),
        }
    }

    pub fn is_valid(&self) -> bool {
        is_valid_username(&self.sender)
            && is_valid_username(&self.channel)
            && is_valid_message(&self.content)
    }

    pub fn sanitize(&mut self) {
        self.channel = self.channel.to_lowercase();
        self.content.truncate(MAX_MESSAGE_LENGTH);
    }
}

impl Hashable for Message {
    fn hash(&self) -> Hash {
        let mut bytes = Vec::new();
        bytes.extend_from_slice(self.content.as_bytes());
        bytes.extend_from_slice(self.sender.as_bytes());
        bytes.extend_from_slice(&self.time.to_be_bytes()[..]);

        Hash::from(&bytes)
    }
}

impl Serialize for Message {
    const SIGNATURE: &'static str = "Message";

    fn serialize_unchecked(&self) -> Vec<u8> {
        let separate_bytes: Vec<Vec<u8>> = vec![
            self.sender.serialize_checked(),
            self.content.serialize_checked(),
            self.channel.serialize_checked(),
            self.time.serialize_checked(),
        ];

        separate_bytes.serialize_unchecked()
    }

    fn deserialize_unchecked<B>(data: B) -> serialize::Result<Self>
    where
        B: AsRef<[u8]>,
        Self: Sized,
    {
        let separate_bytes = SetOfSerializedObjects::deserialize_unchecked(data)?;
        if separate_bytes.len() != 4 {
            return Err(serialize::DeserializationError::InvalidDataLength);
        }

        let sender = String::deserialize_checked(&separate_bytes[0])?;
        let content = String::deserialize_checked(&separate_bytes[1])?;
        let channel = String::deserialize_checked(&separate_bytes[2])?;
        let timestamp = u128::deserialize_checked(&separate_bytes[3])?;

        Ok(Message {
            sender,
            content,
            channel,
            time: timestamp,
        })
    }
}

pub fn is_valid_username(username: &str) -> bool {
    username.len() >= 2
        && username.len() <= MAX_USERNAME_LENGTH
        && username
            .chars()
            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == '#')
}

pub fn is_valid_channel_name(channel_name: &str) -> bool {
    channel_name.len() >= 2
        && channel_name.len() <= MAX_USERNAME_LENGTH
        && channel_name
            .chars()
            .all(|c| (c.is_ascii_alphanumeric() && !c.is_uppercase()) || c == '_' || c == '-' || c == '.' || c == '#')
}

pub fn is_valid_message(message: &str) -> bool {
    message.len() <= MAX_MESSAGE_LENGTH && !message.is_empty()
}

#[derive(Debug, Clone)]
pub struct MessageSanitized {
    pub sender: String,
    pub content: String,
    pub channel: String,
    pub time: u128,
    pub hash: Hash,
}

impl MessageSanitized {
    pub fn remove_blocked_phrases(&mut self, wordlist: &[String], censor_char: char) {
        self.sender = utils::strings::remove_bad_words(&self.sender, &wordlist, censor_char);
        self.content = utils::strings::remove_bad_words(&self.content, &wordlist, censor_char);
        self.channel = utils::strings::remove_bad_words(&self.channel, &wordlist, censor_char);
    }
}

// TODO (low): Change how invalid messages are handled.
impl From<Message> for MessageSanitized {
    fn from(value: Message) -> Self {
        let mut sender = value.sender.clone();
        if !is_valid_username(&sender) {
            sender = format!("{}@{}", INVALID_MESSAGE_IDENT, &value.sender.hash().to_string()[..8]);
        }

        let mut channel = value.channel.clone();
        if !is_valid_username(&channel) {
            channel = format!("{}@{}", INVALID_MESSAGE_IDENT, &value.channel.hash().to_string()[..8]);
        }

        let mut content = value.content.clone();
        if !is_valid_message(&value.content) {
            content = format!("{}@{}", INVALID_MESSAGE_IDENT, &value.content.hash().to_string());
        }

        MessageSanitized {
            sender: sender.replace("\n", ""),
            content: content.replace("\n", ""),
            channel: channel.replace("\n", ""),
            time: value.time,
            hash: value.hash(),
        }
    }
}

impl Hashable for MessageSanitized {
    fn hash(&self) -> Hash {
        self.hash.clone()
    }
}
