use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io;
use std::io::{BufReader, Chain, Cursor, Read, Write};
use std::string::FromUtf8Error;
use url::Url;
use crate::b64::impl_b64::base64_encode;
use crate::handshake_common::{derive_handshake_response, HeaderMap, WEBSOCKET_PROTOCOL_VERSION};
use crate::random::websocket_client_key;
use crate::splittable::Splittable;
use crate::stream::WebsocketStream;

/// Contains the information needed to perform the WebSocket client handshake. Create from a URL with `ClientConnectionInfo::from(url)`,
/// or build from it's components with `ClientConnectionInfo::build()`.
pub struct ClientConnectionInfo {
    url: Url,
    websocket_key: [u8; 16],
    origin: Option<String>,
    extra_headers: HeaderMap
}

#[derive(Debug)]
/// Errors that can happen while sending the client handshake
pub enum ClientHandshakeSendError {
    /// The URL scheme on the URL provided was invalid (not ws or wss)
    InvalidUrlScheme {
        /// The scheme provided
        got_scheme: String
    },
    /// The URL provided does not have a host
    UrlMissingHost,
    /// There was an IO error while trying to write the handshake
    IoError(io::Error)
}
impl Display for ClientHandshakeSendError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ClientHandshakeSendError::InvalidUrlScheme { got_scheme } => {
                write!(f, "invalid url scheme: expected one of 'ws', 'wss', got '{}'", got_scheme)
            },
            ClientHandshakeSendError::UrlMissingHost => {
                write!(f, "invalid url: url missing host")
            }
            ClientHandshakeSendError::IoError(e) => {
                write!(f, "io error: {}", e)
            }
        }
    }
}
impl Error for ClientHandshakeSendError {}

impl From<io::Error> for ClientHandshakeSendError {
    fn from(value: io::Error) -> Self {
        Self::IoError(value)
    }
}

#[derive(Debug)]
/// Errors that can happen while receiving the client handshake response
pub enum ClientHandshakeRecvError {
    /// There was an IO error while trying to read the handshake response
    IoError(io::Error),
    /// Reached an EOF while trying to read the handshake
    UnexpectedEOF,
    /// Received invalid UTF-8 data while trying to parse the handshake
    InvalidUTF8(FromUtf8Error),
    /// Server did not respond with a HTTP/1.1 response
    IncorrectHTTPVersion {
        /// The version that the server actually sent
        server_response: String
    },
    /// WebSocket handshake responses must be 4 lines at minimum, but this response was too short
    ResponseTooShort,
    /// WebSocket handshake responses must be valid HTTP responses, but the header was improperly formatted
    IncorrectHTTPHeader,
    /// The HTTP response indicated failure
    HttpErrorCode {
        /// The error code returned by the server
        code: String,
        /// The error code description returned by the server
        message: String
    },
    /// The handshake response was missing the `Connection: Upgrade` header
    MissingConnectionUpgrade,
    /// The handshake response was missing the `Upgrade: websocket` header
    MissingUpgradeWebsocket,
    /// The handshake response was missing the `Sec-WebSocket-Accept` header
    MissingSecWebsocketAccept,
    /// The handshake response had the `Sec-WebSocket-Accept` header, but it's value was incorrect
    IncorrectSecWebsocketAccept
}
impl Display for ClientHandshakeRecvError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::IoError(e) => {
                write!(f, "io error: {e}")
            },
            Self::UnexpectedEOF => {
                write!(f, "unexpected EOF")
            },
            Self::InvalidUTF8(e) => {
                write!(f, "invalid UTF-8: {e}")
            },
            Self::IncorrectHTTPVersion { server_response } => {
                write!(f, "incorrect HTTP version while reading response, expected HTTP/1.1, got '{server_response}'")
            },
            Self::ResponseTooShort => {
                write!(f, "websocket response invalid (too short)")
            },
            Self::IncorrectHTTPHeader => {
                write!(f, "websocket response invalid (improper http response)")
            },
            Self::HttpErrorCode { code, message } => {
                write!(f, "websocket error: HTTP {code} {message}")
            },
            Self::MissingConnectionUpgrade => write!(f, "websocket response invalid (missing Connection: Upgrade)"),
            Self::MissingUpgradeWebsocket => write!(f, "websocket response invalid (missing Upgrade: websocket)"),
            Self::MissingSecWebsocketAccept => write!(f, "websocket response invalid (missing Sec-WebSocket-Accept)"),
            Self::IncorrectSecWebsocketAccept => write!(f, "websocket response invalid (Sec-WebSocket-Accept incorrect)")
        }
    }
}
impl Error for ClientHandshakeRecvError {}

impl From<io::Error> for ClientHandshakeRecvError {
    fn from(value: io::Error) -> Self {
        Self::IoError(value)
    }
}
impl From<FromUtf8Error> for ClientHandshakeRecvError {
    fn from(value: FromUtf8Error) -> Self {
        Self::InvalidUTF8(value)
    }
}

impl ClientConnectionInfo {
    /// Creates a new `ClientConnectionInfo` with the connection target URL, an optional Origin header value, and
    /// any headers you wish to set in the initial request (cookies, authentication, etc)
    pub fn build(url: Url, origin: Option<String>, extra_headers: HeaderMap) -> Self {
        Self {
            url,
            websocket_key: websocket_client_key(),
            origin,
            extra_headers
        }
    }

    /// Send the WebSocket client handshake over the given stream.
    /// # Errors
    /// This function will return an error if the handshake fails for any reason.
    /// See `ClientHandshakeError` for more details.
    pub fn send_handshake<W: Read + Write>(&self, w: &mut W) -> Result<(), ClientHandshakeSendError> {
        let mut resource_url = self.url.path().to_string();

        if let Some(query) = self.url.query() {
            resource_url += "?";
            resource_url += query;
        }

        debug!("resource URL: {}", resource_url);

        let mut host = self.url.host_str().ok_or(ClientHandshakeSendError::UrlMissingHost)?.to_string();
        if let Some(port) = self.url.port() {
            host += &format!(":{}", port);
        }

        // send the handshake:
        // 1. HTTP GET
        write!(w, "GET {resource_url} HTTP/1.1\r\n")?;
        write!(w, "Host: {}\r\n", host)?;
        write!(w, "Upgrade: websocket\r\n")?;
        write!(w, "Connection: upgrade\r\n")?;
        write!(w, "Sec-WebSocket-Key: {}\r\n", crate::b64::impl_b64::base64_encode(&self.websocket_key))?;

        if let Some(origin) = &self.origin {
            write!(w, "Origin: {origin}\r\n")?;
        }
        write!(w, "Sec-WebSocket-Version: {WEBSOCKET_PROTOCOL_VERSION}\r\n")?;

        // TODO: permessage-deflate (7692)

        for (key, val) in &self.extra_headers {
            write!(w, "{key}: {val}\r\n")?;
        }

        write!(w, "\r\n")?;

        Ok(())
    }

    /// Read the WebSocket handshake response from the given stream, upgrading it to a WebsocketStream.
    /// # Errors
    /// This function will return an error in many circumstances. See `ClientHandshakeRecvError` for details.
    pub fn read_handshake<S: Read + Write + Splittable>(&self, mut stream: S) -> Result<WebsocketStream<Chain<Cursor<Vec<u8>>, S>, S>, ClientHandshakeRecvError> {
        // read until we see the double \r\n
        let mut read_bytes = vec![];
        let mut read_buf = [0u8; 1024];

        let mut extra_bytes = vec![];

        loop {
            let read = stream.read(&mut read_buf)?;

            debug!("read {}", read);

            if read == 0 {
                return Err(ClientHandshakeRecvError::UnexpectedEOF);
            }

            debug!("{:x?}", &read_buf[0..read-1]);

            read_bytes.extend_from_slice(&read_buf[0..read]);

            if read_bytes.ends_with(&[0x0d, 0x0a, 0x0d]) || read_bytes.ends_with(&[0x0d, 0x0a, 0x0d, 0x0a]) {
                // we have hit the end of the server ws handshake
                break;
            }
            // we might have read a bit into the first packet
            // go through one by one to see if it was there
            // this is ok, because its the handshake, and its ok to be slow here
            let mut should_break = false;
            for i in 0..read_bytes.len() {
                let read_slice = &read_bytes[0..i+1];
                if read_slice.ends_with(&[0x0d, 0x0a, 0x0d, 0x0a]) {
                    extra_bytes = read_bytes[i+1..].to_vec();
                    debug!("extra bytes: {:x?}", extra_bytes);
                    read_bytes = read_slice.to_vec();
                    should_break = true;
                    break;
                }
            }
            if should_break { break; }
        }

        debug!("read {:x?} bytes from server, parsing as handshake", read_bytes);

        let handshake_response = String::from_utf8(read_bytes)?;

        trace!("{}", handshake_response);

        let lines = handshake_response.lines().collect::<Vec<_>>();

        if lines.len() < 4 {
            return Err(ClientHandshakeRecvError::ResponseTooShort);
        }

        let http_header_parts = lines[0].split(' ').collect::<Vec<_>>();

        if http_header_parts.len() < 3 {
            return Err(ClientHandshakeRecvError::IncorrectHTTPHeader);
        }

        if http_header_parts[0] != "HTTP/1.1" {
            return Err(ClientHandshakeRecvError::IncorrectHTTPVersion {
                server_response: http_header_parts[0].to_string()
            });
        }

        if http_header_parts[1] != "101" {
            let error_code = http_header_parts[1].to_string();
            let error_response = http_header_parts[2..].join(" ");
            return Err(ClientHandshakeRecvError::HttpErrorCode {
                code: error_code,
                message: error_response
            });
        }

        let mut has_connection = false;
        let mut has_upgrade = false;
        let mut has_sec_accept = false;

        for line in &lines[1..] {
            if line.to_lowercase() == "connection: upgrade" {
                has_connection = true;
            } else if line.to_lowercase() == "upgrade: websocket" {
                has_upgrade = true;
            } else if line.to_lowercase().starts_with("sec-websocket-accept: ") {
                let accept_key = line.split(' ').nth(1).unwrap();

                if derive_handshake_response(&base64_encode(&self.websocket_key)) != accept_key {
                    return Err(ClientHandshakeRecvError::IncorrectSecWebsocketAccept);
                }

                has_sec_accept = true;
            }
        }

        if !has_connection {
            return Err(ClientHandshakeRecvError::MissingConnectionUpgrade);
        }
        if !has_upgrade {
            return Err(ClientHandshakeRecvError::MissingUpgradeWebsocket);
        }
        if !has_sec_accept {
            return Err(ClientHandshakeRecvError::MissingSecWebsocketAccept);
        }

        Ok(WebsocketStream::create(true, stream, extra_bytes))
    }
}

impl From<Url> for ClientConnectionInfo {
    fn from(value: Url) -> Self {
        Self::build(
            value,
            None,
            HeaderMap::new()
        )
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use std::collections::HashMap;
    use std::io::{Cursor};
    use std::net::TcpStream;
    use std::str::FromStr;
    use url::Url;
    use crate::handshake_client::ClientConnectionInfo;
    use crate::tests::setup_logger;

    #[test]
    fn client_handshake_test() {
        setup_logger();

        let mut client = ClientConnectionInfo::build(
            Url::from_str("ws://somesite.com").unwrap(),
            Some("whatever".to_string()),
            HashMap::from([
                ("Authorization".to_string(), "Bearer 12345".to_string())
            ])
        );
        client.websocket_key = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10];

        let mut fakesock = Cursor::new(Vec::new());

        client.send_handshake(&mut fakesock).unwrap();

        assert_eq!(
            String::from_utf8(fakesock.into_inner()).unwrap(),

            "GET / HTTP/1.1\r\n\
            Host: somesite.com\r\n\
            Upgrade: websocket\r\n\
            Connection: upgrade\r\n\
            Sec-WebSocket-Key: AQIDBAUGBwgJCgsMDQ4PEA==\r\n\
            Origin: whatever\r\n\
            Sec-WebSocket-Version: 13\r\n\
            Authorization: Bearer 12345\r\n\r\n"
        );
    }

    #[test]
    fn live_client_test() {
        setup_logger();

        let client = ClientConnectionInfo::from(Url::from_str("ws://10.16.1.1:3204/ws").unwrap());

        let mut tcpstream = TcpStream::connect("10.16.1.1:3204").unwrap();

        client.send_handshake(&mut tcpstream).unwrap();
        client.read_handshake(tcpstream).unwrap();

        panic!();
    }
}