use futures::{AsyncRead, AsyncWrite};
use log::{debug, info, warn};

use darkfi::{Error, Result};

use crate::{
    crypto::encrypt_privmsg,
    privmsg::{Privmsg, MAXIMUM_LENGTH_OF_NICKNAME},
    ChannelInfo,
};

use super::IrcServerConnection;

const RPL_NOTOPIC: u32 = 331;
const RPL_TOPIC: u32 = 332;
const RPL_NAMEREPLY: u32 = 353;
const RPL_ENDOFNAMES: u32 = 366;

impl<C: AsyncRead + AsyncWrite + Send + Unpin + 'static> IrcServerConnection<C> {
    pub(super) fn on_quit(&self) -> Result<()> {
        // Close the connection
        Err(Error::NetworkServiceStopped)
    }

    pub(super) async fn on_receive_user(&mut self) -> Result<()> {
        // We can stuff any extra things like public keys in here.
        // Ignore it for now.
        if self.is_pass_init {
            self.is_user_init = true;
        } else {
            // Close the connection
            warn!("[IRC SERVER] Password is required");
            return self.on_quit()
        }
        Ok(())
    }

    pub(super) async fn on_receive_pass(&mut self, password: &str) -> Result<()> {
        if self.password == password {
            self.is_pass_init = true
        } else {
            // Close the connection
            warn!("[IRC SERVER] Password is not correct!");
            return self.on_quit()
        }
        Ok(())
    }

    pub(super) async fn on_receive_nick(&mut self, nickname: &str) -> Result<()> {
        if nickname.len() > MAXIMUM_LENGTH_OF_NICKNAME {
            return Ok(())
        }

        self.is_nick_init = true;
        let old_nick = std::mem::replace(&mut self.nickname, nickname.to_string());

        let nick_reply = format!(":{}!anon@dark.fi NICK {}\r\n", old_nick, self.nickname);
        self.reply(&nick_reply).await
    }

    pub(super) async fn on_receive_part(&mut self, channels: Vec<String>) -> Result<()> {
        for chan in channels.iter() {
            let part_reply = format!(":{}!anon@dark.fi PART {}\r\n", self.nickname, chan);
            self.reply(&part_reply).await?;
            if self.configured_chans.contains_key(chan) {
                let chan_info = self.configured_chans.get_mut(chan).unwrap();
                chan_info.joined = false;
            }
        }
        Ok(())
    }

    pub(super) async fn on_receive_topic(&mut self, line: &str, channel: &str) -> Result<()> {
        if let Some(substr_idx) = line.find(':') {
            // Client is setting the topic
            if substr_idx >= line.len() {
                return Err(Error::MalformedPacket)
            }

            let topic = &line[substr_idx + 1..];
            let chan_info = self.configured_chans.get_mut(channel).unwrap();
            chan_info.topic = Some(topic.to_string());

            let topic_reply =
                format!(":{}!anon@dark.fi TOPIC {} :{}\r\n", self.nickname, channel, topic);
            self.reply(&topic_reply).await?;
        } else {
            // Client is asking or the topic
            let chan_info = self.configured_chans.get(channel).unwrap();
            let topic_reply = if let Some(topic) = &chan_info.topic {
                format!("{} {} {} :{}\r\n", RPL_TOPIC, self.nickname, channel, topic)
            } else {
                const TOPIC: &str = "No topic is set";
                format!("{} {} {} :{}\r\n", RPL_NOTOPIC, self.nickname, channel, TOPIC)
            };
            self.reply(&topic_reply).await?;
        }
        Ok(())
    }

    pub(super) async fn on_ping(&mut self, value: &str) -> Result<()> {
        let pong = format!("PONG {}\r\n", value);
        self.reply(&pong).await
    }

    pub(super) async fn on_receive_cap(&mut self, line: &str, subcommand: &str) -> Result<()> {
        self.is_cap_end = false;

        let capabilities_keys: Vec<String> = self.capabilities.keys().cloned().collect();

        match subcommand {
            "LS" => {
                let cap_ls_reply = format!(
                    ":{}!anon@dark.fi CAP * LS :{}\r\n",
                    self.nickname,
                    capabilities_keys.join(" ")
                );
                self.reply(&cap_ls_reply).await?;
            }

            "REQ" => {
                let substr_idx = line.find(':').ok_or(Error::MalformedPacket)?;

                if substr_idx >= line.len() {
                    return Err(Error::MalformedPacket)
                }

                let cap: Vec<&str> = line[substr_idx + 1..].split(' ').collect();

                let mut ack_list = vec![];
                let mut nak_list = vec![];

                for c in cap {
                    if self.capabilities.contains_key(c) {
                        self.capabilities.insert(c.to_string(), true);
                        ack_list.push(c);
                    } else {
                        nak_list.push(c);
                    }
                }

                let cap_ack_reply = format!(
                    ":{}!anon@dark.fi CAP * ACK :{}\r\n",
                    self.nickname,
                    ack_list.join(" ")
                );

                let cap_nak_reply = format!(
                    ":{}!anon@dark.fi CAP * NAK :{}\r\n",
                    self.nickname,
                    nak_list.join(" ")
                );

                self.reply(&cap_ack_reply).await?;
                self.reply(&cap_nak_reply).await?;
            }

            "LIST" => {
                let enabled_capabilities: Vec<String> = self
                    .capabilities
                    .clone()
                    .into_iter()
                    .filter(|(_, v)| *v)
                    .map(|(k, _)| k)
                    .collect();

                let cap_list_reply = format!(
                    ":{}!anon@dark.fi CAP * LIST :{}\r\n",
                    self.nickname,
                    enabled_capabilities.join(" ")
                );
                self.reply(&cap_list_reply).await?;
            }

            "END" => {
                self.is_cap_end = true;
            }
            _ => {}
        }
        Ok(())
    }

    pub(super) async fn on_receive_names(&mut self, channels: Vec<String>) -> Result<()> {
        for chan in channels.iter() {
            if !chan.starts_with('#') {
                continue
            }
            if self.configured_chans.contains_key(chan) {
                let chan_info = self.configured_chans.get(chan).unwrap();

                if chan_info.names.is_empty() {
                    return Ok(())
                }

                let names_reply = format!(
                    ":{}!anon@dark.fi {} = {} : {}\r\n",
                    self.nickname,
                    RPL_NAMEREPLY,
                    chan,
                    chan_info.names.join(" ")
                );

                self.reply(&names_reply).await?;

                let end_of_names = format!(
                    ":DarkFi {:03} {} {} :End of NAMES list\r\n",
                    RPL_ENDOFNAMES, self.nickname, chan
                );

                self.reply(&end_of_names).await?;
            }
        }
        Ok(())
    }

    pub(super) async fn on_receive_privmsg(&mut self, line: &str, target: &str) -> Result<()> {
        let substr_idx = line.find(':').ok_or(Error::MalformedPacket)?;

        if substr_idx >= line.len() {
            return Err(Error::MalformedPacket)
        }

        let message = line[substr_idx + 1..].to_string();

        info!("[CLIENT {}] (Plain) PRIVMSG {} :{}", self.peer_address, target, message,);

        let privmsgs_buffer = self.privmsgs_buffer.lock().await;
        let last_term = privmsgs_buffer.last_term() + 1;
        drop(privmsgs_buffer);

        let mut privmsg = Privmsg::new(&self.nickname, target, &message, last_term);

        if target.starts_with('#') {
            if !self.configured_chans.contains_key(target) {
                return Ok(())
            }

            let channel_info = self.configured_chans.get(target).unwrap();

            if !channel_info.joined {
                return Ok(())
            }

            if let Some(salt_box) = &channel_info.salt_box {
                encrypt_privmsg(salt_box, &mut privmsg);
                info!("[CLIENT {}] (Encrypted) PRIVMSG: {:?}", self.peer_address, privmsg);
            }
        } else {
            if !self.configured_contacts.contains_key(target) {
                return Ok(())
            }

            let contact_info = self.configured_contacts.get(target).unwrap();
            if let Some(salt_box) = &contact_info.salt_box {
                encrypt_privmsg(salt_box, &mut privmsg);
                info!("[CLIENT {}] (Encrypted) PRIVMSG: {:?}", self.peer_address, privmsg);
            }
        }

        {
            (*self.seen_msg_ids.lock().await).push(privmsg.id);
            (*self.privmsgs_buffer.lock().await).push(&privmsg)
        }

        self.senders.notify_with_exclude(privmsg.clone(), &[self.subscriber_id]).await;

        debug!(target: "ircd", "PRIVMSG to be sent: {:?}", privmsg);
        self.p2p.broadcast(privmsg).await?;

        Ok(())
    }

    pub(super) async fn on_receive_join(&mut self, channels: Vec<String>) -> Result<()> {
        for chan in channels.iter() {
            if !chan.starts_with('#') {
                continue
            }
            if !self.configured_chans.contains_key(chan) {
                let mut chan_info = ChannelInfo::new()?;
                chan_info.topic = Some("n/a".to_string());
                self.configured_chans.insert(chan.to_string(), chan_info);
            }

            let chan_info = self.configured_chans.get_mut(chan).unwrap();
            if chan_info.joined {
                return Ok(())
            }
            chan_info.joined = true;

            let topic =
                if let Some(topic) = chan_info.topic.clone() { topic } else { "n/a".to_string() };
            chan_info.topic = Some(topic.to_string());

            {
                let j = format!(":{}!anon@dark.fi JOIN {}\r\n", self.nickname, chan);
                let t = format!(":DarkFi TOPIC {} :{}\r\n", chan, topic);
                self.reply(&j).await?;
                self.reply(&t).await?;
            }

            // Send messages in buffer
            if !self.capabilities.get("no-history").unwrap() {
                for msg in self.privmsgs_buffer.lock().await.iter() {
                    if msg.target == *chan {
                        self.senders.notify_by_id(msg.clone(), self.subscriber_id).await;
                    }
                }
            }
        }
        self.on_receive_names(channels).await?;
        Ok(())
    }
}
