use std::env;
use std::io::ErrorKind;
use std::time::Instant;

use tide::{Request, Response, StatusCode};
use tide::http::headers::HeaderValue;
use tide::prelude::*;
use tide::utils::After;
use tide::security::{CorsMiddleware, Origin};
use serde::{Serialize, Deserialize};

use puzzle::Puzzle;
use time::Duration;

mod puzzle;


// fn main() {
//     let puzzle = puzzle::Puzzle::generate_puzzle3(7, 7, 7);
//     println!("{}", puzzle.emoji_visualize());
// }

#[async_std::main]
async fn main() -> tide::Result<()> {

    let mut app = tide::new();

    let frontend_url = match env::var("FRONTEND_URL") {
        Ok(frontend_url) => frontend_url,
        Err(err) => panic!("FRONTEND_URL was not provided as environment variable")
    };

    let cors = CorsMiddleware::new()
        .allow_methods("GET, POST, OPTIONS".parse::<HeaderValue>().unwrap())
        .allow_origin(Origin::from(frontend_url))
        .allow_credentials(true);

    app.with(cors);

    let sessions = tide::sessions::SessionMiddleware::new(
        tide::sessions::MemoryStore::new(),
        b"vpB5ogmn2pCGTKhqZ0Fhl5lWG4LBZjOm2+MOBtiR3abiXZC6L893mYhAIezq9/0c",
    );
    app.with(sessions);

    app.with(After(|mut res: Response| async {
        match res.error() {
            Some(err) => {
                let msg = format!("{{\"message\": \"{:?}\"}}", err);
                res.set_status(res.status());
                res.set_body(msg);
                Ok(res)
            },
            None => Ok(res)
        }
    }));


    app.at("/").get(start_puzzle_game);
    app.at("/").post(check_puzzle_solution);


    app.listen("0.0.0.0:3006").await?;
    Ok(())
}

#[derive(Serialize, Deserialize)]
struct PuzzleSession {
    puzzle: Puzzle,
    #[serde(with = "time::serde::rfc3339")]
    created_at: time::OffsetDateTime,
}

async fn start_puzzle_game(mut req: Request<()>) -> tide::Result {
    let session = req.session_mut();
    let puzzle = puzzle::Puzzle::generate_puzzle3(7, 7, 7);
    let puzzle_session = PuzzleSession {
        puzzle: puzzle.clone(),
        created_at: time::OffsetDateTime::now_utc(),
    };
    match session.insert("current_puzzle", &puzzle_session) {
        Ok(()) => {
            let response = puzzle.leave_only_tail_ends();
        
            let response_json = serde_json::to_string(&response);
            match response_json {
                Ok(response_json) => {
                    Ok(format!("{}", response_json).into())
                },
                Err(err) => Err(err.into())
            }
        },
        Err(err) => Err(err.into())
    }
}

async fn check_puzzle_solution(mut req: Request<()>) -> tide::Result {
    let solution = req.body_json::<Puzzle>();
    match solution.await {
        Ok(solution) => {
            let session = req.session_mut();
            let solved_puzzle: Option<PuzzleSession> = session.get("current_puzzle");
            match solved_puzzle {
                Some(PuzzleSession{puzzle, created_at}) => {
                    println!("Checking if");
                    println!("{}", solution.emoji_visualize());
                    println!("Equals");
                    println!("{}", puzzle.emoji_visualize());
                    let time_now = time::OffsetDateTime::now_utc();
                    let duration = time_now - created_at;
                    if duration < Duration::new(5, 0) {
                        if puzzle.equal(&solution) {
                            let flag = env::var("FLAG");
                            match flag {
                                Ok(flag) => Ok(format!("{{\"message\": \"{}\", \"time\": {}}}", flag, duration.as_seconds_f64()).into()),
                                Err(err) => Err(tide::Error::from_str(404, format!("Someone forgot to provide the flag. Please alert this to the CTF team :P (this is not a joke). Error: {:?}", err)))
                            }
                        } else {
                            Err(tide::Error::from_str(406, "Puzzle is incorrect. You will have to retry the game by sending a GET request to /"))
                        }
                    } else {
                        Err(tide::Error::from_str(406, "You ran out of time. You must finish the puzzle within 5 seconds"))
                    }
                },
                None => Err(tide::Error::from_str(405, "You must first start the game by sending a GET request to /"))
            }
        },
        Err(err) => Err(err.into())
    }
}