use std::env::args;

use utils::{
    binary::checksum, http::{self, Request, Response}, strings::StaticString
};

const MAX_MESSAGE_LENGTH: usize = 2048;
const MESSAGE_HISTORY_LENGTH: usize = 64;
const DEFAULT_PORT: u16 = 13337;

#[derive(Debug)]
struct Appdata {
    messages: [StaticString<MAX_MESSAGE_LENGTH>; MESSAGE_HISTORY_LENGTH],
    index: usize,
}

impl Appdata {
    pub fn new() -> Self {
        Self {
            messages: core::array::from_fn(|_| StaticString::new()),
            index: 0,
        }
    }

    pub fn insert_message(&mut self, message: String) -> Result<(), ()> {
        if message.len() > MAX_MESSAGE_LENGTH {
            return Err(());
        }

        let index = self.index;
        self.messages[index] = message.into();
        self.index = (index + 1) % self.messages.len();

        Ok(())
    }

    pub fn get_messages(&self) -> Vec<String> {
        let mut messages = vec![];

        for message in self.messages.iter() {
            if message.is_empty() {
                continue;
            }
            messages.push(String::from(message));
        }

        messages
    }
}

fn index_get_sync(state: &mut Appdata, _: Request) -> Response {
    let string = state.get_messages().join(",");
    println!("Sending `{}` characters", string.len());
    Response::ok().body(&string)
}

fn index_post_sync(state: &mut Appdata, request: Request) -> Response {
    let body = request.body;

    let Some(message_ser_bin) = utils::binary::hex2bin(&body) else {
        return Response::bad_request();
    };

    if message_ser_bin.len() < 2 {
        return Response::bad_request();
    }

    // Checksum before decryption
    if checksum(&message_ser_bin[..message_ser_bin.len()-1]) != message_ser_bin[message_ser_bin.len()-1] {
        return Response::bad_request();
    }

    println!("Received ({}): {:#?}", body.len(), body);

    match state.insert_message(body.clone()) {
        Ok(()) => Response::ok().body(&body),
        Err(()) => Response::bad_request(),
    }
}

fn router(state: &mut Appdata, request: Request) -> Response {
    if request.url != "/" {
        return Response::not_found();
    }

    match request.method {
        http::Method::Get => index_get_sync(state, request),
        http::Method::Post => index_post_sync(state, request),
        http::Method::Other => Response::method_not_allowed(),
    }
}

fn main() -> http::Result<()> {
    let port = args().nth(1).unwrap_or(DEFAULT_PORT.to_string());

    println!("Starting on port {}", port);

    let appdata = Appdata::new();
    http::run(&format!("127.0.0.1:{}", port), appdata, router)?;

    Ok(())
}
