use std::io::{Write};
use std::net::TcpStream;
use std::sync::Arc;
use rustls::{ClientConnection, RootCertStore, StreamOwned};
use crate::pop3::line::LineReader;

pub enum Pop {
    StreamTls(StreamOwned<ClientConnection, TcpStream>, LineReader),
    Stream(TcpStream, LineReader),
}

impl Pop {
    pub fn new(host: String, port: String) -> Result<Pop, String> {
        let ssl = if port == 995.to_string() {
            true
        } else {
            false
        };
        let line_reader = LineReader::new();

        let res = match TcpStream::connect(format!("{}:{}", host, port)) {
            Ok(stream) => {
                if ssl {
                    let mut root_store = RootCertStore::empty();
                    let load_native_certs = match rustls_native_certs::load_native_certs() {
                        Ok(e) => e,
                        Err(e) => {
                            return Err(format!("证书加载失败: {}", e.to_string()));
                        }
                    };

                    for cert in load_native_certs {
                        match root_store.add(&rustls::Certificate(cert.0)) {
                            Ok(_) => {}
                            Err(e) => {
                                return Err(format!("证书加载失败: {}", e.to_string()));
                            }
                        };
                    }

                    let config = rustls::ClientConfig::builder()
                        .with_safe_defaults()
                        .with_root_certificates(root_store)
                        .with_no_client_auth();

                    let server_name = host.as_str().try_into().unwrap();
                    let connection = match ClientConnection::new(Arc::new(config), server_name) {
                        Ok(e) => e,
                        Err(e) => {
                            return Err(format!("连接加载失败: {}", e.to_string()));
                        }
                    };
                    Pop::StreamTls(StreamOwned::new(connection, stream), line_reader)
                } else {
                    Pop::Stream(stream, line_reader)
                }
            }
            Err(e) => {
                return Err(format!("连接加载失败: {}", e.to_string()));
            }
        };
        Ok(res)
    }
    pub fn login(&mut self, name: String, pass: String) -> Result<(), String> {
        self.write(format!("USER {}\r\n", name).as_bytes())?;
        self.read_status()?;
        self.read_status()?;
        self.write(format!("PASS {}\r\n", pass).as_bytes())?;
        self.read_status()?;
        Ok(())
    }

    fn read_status(&mut self) -> Result<String, String> {
        match self.read() {
            Ok(e) => {
                if e.starts_with("+OK") {
                    let e = e.trim().trim_start_matches("+OK").trim_end_matches("\r\n");
                    return Ok(e.trim().to_string());
                }
                if e.starts_with("-ERR") {
                    let e = e.trim().trim_start_matches("-ERR").trim_end_matches("\r\n");
                    return Err(e.trim().to_string());
                }
                Err(e)
            }
            Err(e) => {
                Err(e)
            }
        }
    }
    fn write(&mut self, data: &[u8]) -> Result<usize, String> {
        let res = match self {
            Pop::StreamTls(e, _) => e.write(data),
            Pop::Stream(e, _) => e.write(data),
        };
        match res {
            Ok(e) => {
                Ok(e)
            }
            Err(e) => {
                Err(format!("写入失败: {}", e.to_string()))
            }
        }
    }
    fn read(&mut self) -> Result<String, String> {
        let res = match self {
            Pop::StreamTls(e, reader) => {
                reader.read_line(e)
            }
            Pop::Stream(e, reader) => {
                reader.read_line(e)
            }
        };
        match res {
            Ok(e) => Ok(e),
            Err(e) => {
                Err(format!("连接读取信息失败: {}", e.to_string()))
            }
        }
    }
    fn read_multi(&mut self) -> Result<Vec<String>, String> {
        self.read_status()?;
        let mut response: Vec<String> = vec![];
        loop {
            let line = match self {
                Pop::StreamTls(stre, reader) => reader.read_line(stre),
                Pop::Stream(stre, reader) => reader.read_line(stre)
            };
            match line {
                Ok(e) => {
                    match e {
                        _ if e == ".\r\n" => {
                            break;
                        }
                        _ if e.starts_with(".") => {
                            response.push(e[1..].to_string());
                            break;
                        }
                        _ => { response.push(e); }
                    };
                }
                Err(e) => {
                    return Err(e.to_string());
                }
            }
        }
        Ok(response)
    }
    pub fn stat(&mut self) -> Result<Pop3Stat, String> {
        self.write(format!("STAT\r\n").as_bytes())?;
        return match self {
            Pop::StreamTls(_, _) => {
                let res = self.read_status()?;
                let res = res.trim().split(" ").collect::<Vec<&str>>();
                Ok(Pop3Stat { message_count: res[0].parse().unwrap(), maildrop_size: res[1].parse().unwrap() })
            }
            Pop::Stream(_, _) => {
                let res = self.read_status()?;
                let res = res.trim().split(" ").collect::<Vec<&str>>();
                Ok(Pop3Stat { message_count: res[0].parse().unwrap(), maildrop_size: res[1].parse().unwrap() })
            }
        };
    }
    pub fn msg_size(&mut self, message_id: u32) -> Result<u32, String> {
        self.write(format!("LIST {}\r\n", message_id).as_bytes())?;
        return match self {
            Pop::StreamTls(_, _) => {
                let res = self.read_status()?;
                let res = res.trim().split(" ").collect::<Vec<&str>>();
                Ok(res[1].parse().unwrap())
            }
            Pop::Stream(_, _) => {
                let res = self.read_status()?;
                let res = res.trim().split(" ").collect::<Vec<&str>>();
                Ok(res[1].parse().unwrap())
            }
        };
    }
    pub fn list(&mut self) -> Result<Vec<Pop3MessageInfo>, String> {
        self.write(format!("LIST\r\n").as_bytes())?;
        let mut message_info = vec![];
        let list = match self {
            Pop::StreamTls(_, _) => {
                self.read_multi()?
            }
            Pop::Stream(_, _) => {
                self.read_multi()?
            }
        };
        for item in list {
            let res = item.trim().split(" ").collect::<Vec<&str>>();
            message_info.push(Pop3MessageInfo { message_id: res[0].parse().unwrap(), message_size: res[1].parse().unwrap() });
        }
        Ok(message_info)
    }
    pub fn retr(&mut self, message_id: u32) -> Result<Vec<u8>, String> {
        self.write(format!("RETR {}\r\n", message_id).as_bytes())?;
        return match self {
            Pop::StreamTls(_, _) => {
                let res = self.read_multi()?;
                let res = res.join("").as_bytes().to_vec();
                Ok(res)
            }
            Pop::Stream(_, _) => {
                let res = self.read_multi()?;
                let res = res.join("").as_bytes().to_vec();
                Ok(res)
            }
        };
    }
}

/// POP3 maildrop statistics
pub struct Pop3Stat {
    /// count of massages in the maildrop
    pub message_count: u32,

    /// size of the maildrop in bytes
    pub maildrop_size: u32,
}

/// POP3 message info
pub struct Pop3MessageInfo {
    /// numerical Id of the message used for various commands
    pub message_id: u32,

    /// size of the message in bytes
    pub message_size: u32,
}