#![allow(clippy::needless_pass_by_value)]
use std::{env, sync::Arc, time::Instant};

use battlesnake::{
    logic::{self, GameState},
    Response,
};
use battlesnake_game_types::{types::Move, wire_representation::Game};
use dashmap::DashMap;
use log::{error, info};
use rocket::{
    fairing::AdHoc, get, http::Status, launch, post, routes, serde::json::Json, tokio::task, State,
};
use serde_json::Value;

type States = Arc<DashMap<(String, String), GameState>>;

#[get("/")]
fn handle_index() -> Json<Value> {
    Json(logic::info())
}

#[post("/start", format = "json", data = "<game>")]
fn handle_start(state: &State<States>, game: Json<Game>) -> Status {
    if state
        .insert(
            (game.game.id.clone(), game.you.id.clone()),
            logic::start(&game),
        )
        .is_some()
    {
        error!("re-started game");
    }

    Status::Ok
}

#[post("/move", format = "json", data = "<game>")]
async fn handle_move(state: &State<States>, game: Json<Game>) -> Json<Response> {
    let start = Instant::now();
    let state = (*state).clone();
    let action = task::spawn_blocking(move || {
        let mut game_state = state.get_mut(&(game.game.id.clone(), game.you.id.clone()));
        while game_state.is_none() {
            error!("move request without previous start");
            if state
                .insert(
                    (game.game.id.clone(), game.you.id.clone()),
                    logic::start(&game),
                )
                .is_some()
            {
                error!("re-started game");
            }
            game_state = state.get_mut(&(game.game.id.clone(), game.you.id.clone()));
        }
        let Some(mut game_state) = game_state else {
            std::mem::drop(game_state);
            unreachable!()
        };
        logic::get_move(game.0, &mut game_state, &start)
    })
    .await
    .unwrap_or(Move::Up);
    Json(Response::new(action))
}

#[post("/end", format = "json", data = "<game>")]
fn handle_end(state: &State<States>, game: Json<Game>) -> Status {
    if let Some((_key, game_state)) = state.remove(&(game.game.id.clone(), game.you.id.clone())) {
        logic::end(&game, game_state);
    } else {
        error!("ended game without state");
    }

    Status::Ok
}

#[launch]
fn rocket() -> _ {
    // Lots of web hosting services expect you to bind to the port specified by the `PORT`
    // environment variable. However, Rocket looks at the `ROCKET_PORT` environment variable.
    // If we find a value for `PORT`, we set `ROCKET_PORT` to that value.
    if let Ok(port) = env::var("PORT") {
        env::set_var("ROCKET_PORT", &port);
    }

    // We default to 'info' level logging. But if the `RUST_LOG` environment variable is set,
    // we keep that value instead.
    if env::var("RUST_LOG").is_err() {
        env::set_var("RUST_LOG", "info");
    }

    env_logger::init();

    info!("Starting Battlesnake Server...");

    rocket::build()
        .attach(AdHoc::on_response("Server ID Middleware", |_, res| {
            Box::pin(async move {
                res.set_raw_header("Server", "battlesnake/github/starter-snake-rust");
            })
        }))
        .mount(
            "/",
            routes![handle_index, handle_start, handle_move, handle_end],
        )
        .manage(States::new(DashMap::new()))
}
