use std::{io, path::PathBuf, ops::Deref};

use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWrite, AsyncRead};

use crate::{handling::{
    file_handlers::NamedFile,
    methods::Method,
    request::Request,
    response::{Outcome, Response, ResponseBody, Status},
    routes::{Body, Data},
}, setup::MountPoint};

/// The Maximal size of the Body of an HTTP-Message in bytes
static MAX_HTTP_MESSAGE_SIZE: u16 = 4196;

/// Function which handles a TCP Connection according to http-Standards by taking in a
/// [tokio::net::TcpStream] and a [`Vec<MountPoint<'_>>`]. 
///
/// Firstly validates the headers and body, Aborts with error messages if it finds faults.
///
/// Secondly matches the request with a route in the mountpoint Vector and executes its handler
/// function. If it fails, checks for another, if nothing is found writes back a
/// [Status::NotFound]. If the handler function could respond it uses the [Response] of the handler
///
/// # Panics
/// No Panics
pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint>) {
    let mut buf_reader = BufReader::new(&mut stream);
    let mut http_request: Vec<String> = Vec::with_capacity(10);
    loop {
        let mut buffer = String::new();
        if buf_reader.read_line(&mut buffer).await.is_err() {
            eprintln!("\x1b[31mAborting due to invalid UTF-8 in request header\x1b[0m");
            return;
        }
        if buffer == "\r\n" {
            break;
        }
        http_request.push(buffer);
    }

    let request_status_line = if let Some(status_line) = http_request.get(0).cloned() {
        status_line
    } else {
        eprintln!("\x1b[31mAborting due to missing headers\x1b[0m");
        return;
    };

    let mut request = Request {
        uri: if let Some(uri) = &request_status_line.split(' ').nth(1) {
            if let Ok(uri) = uri.deref().try_into() {
                uri
            } else {
                eprintln!("\x1b[31mAborting due to invalid uri\x1b[0m");
                return;
            }
        } else {
            eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
            return;
        },
        cookies: Request::extract_cookies_from_vec(&mut http_request),
        headers: http_request,
        mime_type: None,
        method: if let Some(method) = request_status_line.split(' ').next() {
            if let Ok(ok) = method.parse() {
                ok
            } else {
                eprintln!("\x1b[31mAborting due to invalid request method\x1b[0m");
                return;
            }
        } else {
            eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
            return;
        },
    };

    let mut data = Data {
        is_complete: false,
        buffer: vec![],
    };
    if request.can_have_body() {
        let length = if let Some(len) = request
            .headers
            .iter()
            .filter(|header| header.starts_with("Content-Length: "))
            .map(|header| {
                let header = header.strip_prefix("Content-Length: ").unwrap();
                header.trim().parse::<usize>()
            })
            .next()
        {
            if let Ok(size) = len {
                size
            } else {
                eprintln!(
                    "\x1b[31m`{}` must have a `Content-Length` header\x1b[0m",
                    request.method
                );
                if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
                    error_occured_when_writing(e)
                };
                return;
            }
        } else {
            if request.mandatory_body() {
                eprintln!(
                    "\x1b[31m`{}` must have a `Content-Length` header\x1b[0m",
                    request.method
                );
                if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
                    error_occured_when_writing(e)
                };
                return;
            }
            0
        };
        if length != 0 {
            request.mime_from_headers();
            let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()];
            let read = buf_reader.read(&mut buffer).await.unwrap();
            if read != length {
                if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
                    error_occured_when_writing(e)
                };
                return;
            }
            data.is_complete = true;
            data.buffer = buffer;
        }
    }

    let mut handled_response: Option<Outcome<Response, Status, Data>> = None;
    for mountpoint in mountpoints {
        if !mountpoint.compare_with_uri(&mut request.uri) {
            continue;
        }
        for route in mountpoint.routes {
            if (route.method != request.method)
                && ((route.method != Method::Get) && (request.method == Method::Head))
            {
                continue;
            }
            if !route.uri.compare_uri(&request.uri) {
                continue;
            }
            handled_response = Some((route.handler)(request.clone(), data.clone()));

            if let Some(Outcome::Forward(_)) = handled_response {
                continue;
            }
            break;
        }
    }

    let response = match handled_response {
        Some(val) => match val {
            Outcome::Success(success) => success,
            Outcome::Failure(error) => failure_handler(error),
            Outcome::Forward(_) => failure_handler(Status::NotFound),
        },
        None => failure_handler(Status::NotFound),
    };

    if let Err(e) = response.write(stream, Some(request)).await {
        eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
    }
}

/// Dumb function that renders a 404 page from any status given. but still writes that status code.
fn failure_handler(status: Status) -> Response {
    let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap();
    Response {
        cookies: None,
        headers: vec![],
        status: Some(status),
        body: Box::new(Body::new(page_404.get_data(), page_404.get_mime())),
    }
}

/// Handler for len_not_defined errors. writes back directly
async fn len_not_defined<T: AsyncRead + AsyncWrite + std::marker::Unpin>(stream: T, status: Status) -> io::Result<()> {
    let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap();
    Response {
        cookies: None,
        headers: vec![],
        status: Some(status),
        body: Box::new(Body::new(page_411.get_data(), page_411.get_mime())),
    }
    .write(stream, None)
    .await?;
    Ok(())
}

/// takes in an io error and writes it back in the server console to the user if writing failed
fn error_occured_when_writing(e: io::Error) {
    eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
}