use anyhow::Result;
use std::io::Write;
use std::net::TcpStream;

#[derive(Debug)]
pub(crate) struct HttpParseError {
    pub(crate) status: u16,
    pub(crate) message: String,
}

impl HttpParseError {
    pub(crate) fn bad_request(message: impl Into<String>) -> Self {
        Self {
            status: 400,
            message: message.into(),
        }
    }

    pub(crate) fn request_entity_too_large(message: impl Into<String>) -> Self {
        Self {
            status: 413,
            message: message.into(),
        }
    }

    pub(crate) fn unsupported(message: impl Into<String>) -> Self {
        Self {
            status: 501,
            message: message.into(),
        }
    }
}

pub(crate) fn write_http_error(stream: &mut TcpStream, status: u16, message: &str) -> Result<()> {
    let reason = reason_phrase(status);
    let body = format!(
        "{{\n  \"error\": \"{status} {reason}\",\n  \"message\": \"{}\"\n}}\n",
        escape_json(message)
    );
    let mut response = format!(
        "HTTP/1.1 {status} {reason}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
        body.len()
    );
    response.push_str(&body);
    stream.write_all(response.as_bytes())?;
    stream.flush()?;
    Ok(())
}

pub(crate) fn escape_json(value: &str) -> String {
    value
        .replace('\\', "\\\\")
        .replace('"', "\\\"")
        .replace('\n', "\\n")
}

pub(crate) fn reason_phrase(status: u16) -> &'static str {
    match status {
        200 => "OK",
        204 => "No Content",
        400 => "Bad Request",
        401 => "Unauthorized",
        403 => "Forbidden",
        404 => "Not Found",
        405 => "Method Not Allowed",
        409 => "Conflict",
        413 => "Payload Too Large",
        501 => "Not Implemented",
        503 => "Service Unavailable",
        505 => "HTTP Version Not Supported",
        500 => "Internal Server Error",
        _ => "BucketWarden",
    }
}

pub(crate) fn reject_overloaded_connection(mut stream: TcpStream) -> Result<()> {
    let response =
        b"HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\nContent-Length: 0\r\n\r\n";
    stream.write_all(response)?;
    stream.flush()?;
    Ok(())
}
