use std::convert::From;
use std::default::Default;
use std::fmt;
use std::io::{self, BufRead, BufReader, Cursor, Read};
use std::mem;
use std::net::TcpStream;
use std::str;

#[derive(Debug)]
pub struct Request {
    pub version: (u8, u8),
    pub method: String,
    pub path: String,
    pub headers: Vec<(String, String)>,
    pub body: Vec<u8>,
    error: Option<String>,
    is_parsed: bool,
    last_header_field: Option<String>,
    last_header_value: Option<String>,
}

impl Request {
    pub fn is_head(&self) -> bool {
        self.method == "HEAD"
    }

    #[allow(clippy::missing_const_for_fn)]
    pub fn is_ok(&self) -> bool {
        self.error.is_none()
    }

    #[allow(clippy::missing_const_for_fn)]
    pub fn is_err(&self) -> bool {
        self.error.is_some()
    }

    #[allow(clippy::missing_const_for_fn)]
    pub fn error(&self) -> Option<&String> {
        self.error.as_ref()
    }

    pub fn find_header_values(&self, searched_field: &str) -> Vec<&str> {
        self.headers
            .iter()
            .filter_map(|(field, value)| {
                if field == searched_field {
                    Some(value.as_str())
                } else {
                    None
                }
            })
            .collect()
    }

    fn record_last_header(&mut self) {
        if self.last_header_field.is_some() && self.last_header_value.is_some() {
            let last_header_field = mem::replace(&mut self.last_header_field, None).unwrap();
            let last_header_value = mem::replace(&mut self.last_header_value, None).unwrap();
            self.headers
                .push((last_header_field.to_lowercase(), last_header_value));
        }
    }

    fn content_length(&self) -> Option<usize> {
        use std::str::FromStr;

        self.find_header_values("content-length")
            .first()
            .and_then(|len| usize::from_str(*len).ok())
    }

    fn has_chunked_body(&self) -> bool {
        self.headers
            .iter()
            .filter(|(name, _)| name == "transfer-encoding")
            .any(|(_, value)| value.contains("chunked"))
    }

    fn read_request_body(&mut self, mut raw_body_rd: impl Read) -> io::Result<()> {
        if let Some(content_length) = self.content_length() {
            let mut rd = raw_body_rd.take(content_length as u64);
            return io::copy(&mut rd, &mut self.body).map(|_| ());
        }

        if self.has_chunked_body() {
            let mut chunk_size_buf = String::new();
            let mut reader = BufReader::new(raw_body_rd);

            loop {
                reader.read_line(&mut chunk_size_buf)?;

                let chunk_size = {
                    let chunk_size_str = chunk_size_buf.trim_matches(|c| c == '\r' || c == '\n');

                    u64::from_str_radix(chunk_size_str, 16)
                        .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?
                };

                if chunk_size == 0 {
                    break;
                }

                io::copy(&mut (&mut reader).take(chunk_size), &mut self.body)?;

                chunk_size_buf.clear();
                reader.read_line(&mut chunk_size_buf)?;
            }

            return Ok(());
        }

        if self.version == (1, 0) {
            return io::copy(&mut raw_body_rd, &mut self.body).map(|_| ());
        }

        Ok(())
    }
}

impl Default for Request {
    fn default() -> Self {
        Self {
            version: (1, 1),
            method: String::new(),
            path: String::new(),
            headers: Vec::new(),
            body: Vec::new(),
            error: None,
            is_parsed: false,
            last_header_field: None,
            last_header_value: None,
        }
    }
}

impl<'a> From<&'a TcpStream> for Request {
    fn from(mut stream: &TcpStream) -> Self {
        let mut request = Self::default();

        let mut all_buf = Vec::new();

        loop {
            if request.is_parsed {
                break;
            }

            let mut headers = [httparse::EMPTY_HEADER; 16];
            let mut req = httparse::Request::new(&mut headers);
            let mut buf = [0; 1024];

            let rlen = match stream.read(&mut buf) {
                Err(e) => Err(e.to_string()),
                Ok(0) => Err("Nothing to read.".into()),
                Ok(i) => Ok(i),
            }
            .map_err(|e| request.error = Some(e))
            .unwrap_or(0);

            if rlen == 0 {
                break;
            }

            all_buf.extend_from_slice(&buf[..rlen]);

            let _ = req
                .parse(&all_buf)
                .map_err(|e| {
                    request.error = Some(e.to_string());
                    request.is_parsed = true;
                })
                .map(|status| match status {
                    httparse::Status::Complete(head_length) => {
                        if let Some(a @ 0..=1) = req.version {
                            request.version = (1, a);
                        }

                        if let Some(a) = req.method {
                            request.method += a;
                        }

                        if let Some(a) = req.path {
                            request.path += a
                        }

                        for h in req.headers {
                            request.last_header_field = Some(h.name.to_lowercase());
                            request.last_header_value =
                                Some(String::from_utf8_lossy(h.value).to_string());

                            request.record_last_header();
                        }

                        let raw_body_rd = Cursor::new(&all_buf[head_length..]).chain(stream);

                        if let Err(err) = request.read_request_body(raw_body_rd) {
                            request.error = Some(err.to_string());
                        }

                        request.is_parsed = true;
                    }
                    httparse::Status::Partial => (),
                });
        }

        request
    }
}

impl fmt::Display for Request {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "\r\n{} {}\r\n", &self.method, &self.path)?;

        for &(ref key, ref value) in &self.headers {
            writeln!(f, "{}: {}\r", key, value)?;
        }

        if self.body.is_empty() {
            write!(f, "")
        } else {
            writeln!(f, "{}\r", &String::from_utf8_lossy(&self.body))
        }
    }
}
