use magic_crypt::{new_magic_crypt, MagicCryptTrait};
use std::fs;
use serde::{Serialize, Deserialize};
use serde_derive::{Serialize, Deserialize};
use actix_web::{get, post, web, App, HttpResponse, HttpServer, HttpRequest, Responder};
use actix_web::http::header::HeaderValue;
use std::fs::OpenOptions;
use std::io::prelude::*;
use chrono::Local;
use rand::Rng;

#[derive(Deserialize)]
struct Post {
    content: String,
    name: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
struct EncryptedPost {
    name: String,
    crypt: String,
    date: String,
    id: u64,
}

fn decrypt(encrypted: String, pass: &str) -> Result<String, String> {
    let mc = new_magic_crypt!(pass, 256);
    let result = mc.decrypt_base64_to_string(&encrypted);
    match result {
        Ok(v) => {
            return Ok(v);
        },
        Err(_) => {
            return Err("Could not decrypt".to_string());
        },
    }
}

fn encrypt(content: &String, password: &String) -> String {
    let mc = new_magic_crypt!(password, 256);
    let encrypted = mc.encrypt_str_to_base64(&content);
    return String::from(encrypted);
}

fn save_post(text: &str) {
    let mut file = OpenOptions::new()
        .write(true)
        .append(true)
        .create(true)
        .open("./Files/posts.json")
        .unwrap();

    if let Err(e) = writeln!(file, "{}", text) {
        eprintln!("Could not save the post, got {}", e);
    };
}

fn get_all_posts() -> (Vec<EncryptedPost>, String) {
    let posts = fs::read_to_string("./Files/posts.json").unwrap();
    let mut res = Vec::new();
    let mut display = String::from("\n<table title=\"Latest Posts\">\n<caption>Latest Posts</caption>\n");
    for line in posts.lines() {
        let post: EncryptedPost = serde_json::from_str(&line).unwrap();
        res.push(post.clone());
        display.push_str(&format!("<tr>\n<td>{}</td>\n<td>{}</td>\n</tr>\n<br>\n", post.date, post.id));
    }
    display.push_str("</table>");
    return (res, display);
}

#[post("/")]
async fn post_index(web::Form(form): web::Form<Post>, req: HttpRequest) -> impl Responder {
    let hostname = req.headers().get("Host").unwrap().to_str().unwrap();
    let pass = random_password();
    let encrypted = encrypt(&form.content, &pass);
    let now = Local::now();
    let now = now.format("%d/%m/%Y@%H:%M:%S");
    let mut rng = rand::thread_rng();
    let id: u64 = rng.gen::<u64>();
    let my_encrypted = EncryptedPost { name: form.name.clone(), crypt: encrypted.clone(), date: format!("{}", now), id: id };
    let json = serde_json::to_string(&my_encrypted).unwrap();
    save_post(&json);
    let header = fs::read_to_string("./Files/header.html").unwrap_or("".to_string());
    let ret = format!("{}\nPost saved <a href=\"http://{}/decrypt/{}/{}\">here</a>", header, hostname, id, pass);
    return HttpResponse::Ok().body(ret);
}

fn random_password() -> String {
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                            abcdefghijklmnopqrstuvwxyz\
                            0123456789";
    let mut rng = rand::thread_rng();
    let password: String = (0..64)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect();
    return password;
}

#[get("/decrypt/{givenid}/{password}")] // <- define path parameters
async fn show_post(path: web::Path<(u64, String)>, req: HttpRequest) -> impl Responder {
    let (givenid, password) = path.into_inner();
    let (posts, _) = get_all_posts();
    let mut ret_text = fs::read_to_string("./Files/header.html").unwrap();
    let placeholder = HeaderValue::from_static("Please ignore");
    let useragent = req.headers()
        .get("user-agent")
        .unwrap_or(&placeholder)
        .to_str()
        .unwrap_or("Please ignore");

    for post in posts {
        if givenid == post.id {

            //Change the output for wget and curl
            if useragent.starts_with("curl") || useragent.starts_with("Wget") {
                let mut content = decrypt(post.crypt, &password)
                                .unwrap_or("Error decrypting".to_string());
                content.push_str("\n");
                return HttpResponse::Ok().body(content);
            }

            let _ = &mut ret_text.push_str(&format!("\n\
<b>Save the link to this page ! We will not store it anywhere.</b><br><br><i>\
This page can be also be accessed using curl or wget.</i><hr>\n\
<h2>{}</h2>\n<i>{}</i><br><br>\n<pre>\n", post.name, post.date));

            let _ = match decrypt(post.crypt, &password) {
                Ok(v) => {
                    let _ = &mut ret_text.push_str(&v);
                    let _ = &mut ret_text.push_str("\n</pre>\n");
                    ret_text.push_str("</body>\n</html>");
                    break;
                },
                Err(_) => {
                    *&mut ret_text = String::from("Could not decrypt your post.");
                    break;
                },
            };
        }
    }
    if ret_text.len() == 0 {
        *&mut ret_text.push_str("Something went wrong. Does the post exist ?");
    }
    return HttpResponse::Ok().body(ret_text);

}
#[get("/")]
async fn get_index() -> impl Responder {
    let mut page = fs::read_to_string("./Files/index.html").unwrap();
    let (_, mut posts) = get_all_posts();
    posts.push_str("\n</body>\n</html>");
    page.push_str(&posts);
    return HttpResponse::Ok().body(page);
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {

    HttpServer::new(move || {
        App::new()
            .app_data(web::FormConfig::default().limit(1024 * 1000))
            .service(get_index)
            .service(post_index)
            .service(show_post)
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await
}
