use log::{info, error};
use std::env;
use std::fs;

use anyhow::bail;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, middleware::Logger, http::header::ContentType};
use actix_web::error::HttpError;
use awc::Client;
use serde::{Deserialize, Serialize};
use serde_json;
use tera::{Tera, Context};
use gethostname::gethostname;

#[derive(Serialize, Deserialize, Debug)]
struct Movie {
    title: String,
    director: String,
    release_year: u16,
}

fn gen_error_message(error: &str) -> String {
    let hostname = gethostname().into_string().unwrap();
    format!("[served by {}], {}", hostname, error)
}

fn parse_database_url() -> String {
    let mut database_url: String = match env::var("DATABASE_URL") {
	Ok(database_url) => database_url.to_string(),
	Err(_) => panic!("You must set DATABASE_URL env var")
    };

    if !database_url.contains("http://") {
	database_url = "http://".to_owned() + database_url.as_str();
    }

    database_url = match database_url.chars().last().unwrap() {
	'/' => database_url,
	_ => database_url + "/",
    };

    database_url
}

async fn render(is_ok: bool, error_message: &str) -> anyhow::Result<String>  {
    let hostname = gethostname().into_string().unwrap();
    
    let client = Client::default();
    let movies = match client.get(parse_database_url()).send().await {
	Ok(mut res) => match res.json::<Vec<Movie>>().await {
	    Ok(movies) => movies,
	    Err(e) => {
		error!("{}", e.to_string());
		bail!(e.to_string());
	    },
	},
	Err(e) => {
	    error!("{}", e.to_string());
	    bail!(e.to_string());
	},
    };


    let tera = match Tera::new("templates/*.html") {
	Ok(t) => t,
	Err(e) => {
            println!("Parsing error(s): {}", e);
            ::std::process::exit(1);
	}
    };

    let mut context = Context::new();
    context.insert("hostname", &hostname);
    context.insert("movies", &movies);

    context.insert("error_message", &error_message);
    context.insert("is_ok", &is_ok);

    return match tera.render("index.html", &context) {
	Ok(page) => Ok(page),
	Err(e) => {
	    error!("{}", e.to_string());
	    bail!(e.to_string());
	},
    }
    
}

#[get("/")]
async fn index() -> Result<impl Responder, HttpError> {

    match render(true, "").await {
	Ok(page) => Ok(HttpResponse::Ok()
		       .content_type(ContentType::plaintext())
		       .insert_header(("Content-Type", "text/html"))
		       .body(page)),
	Err(e) => {
	    if e.to_string() == "Failed to connect to host: Connection refused (os error 111)".to_string() {
		Ok(HttpResponse::InternalServerError().body(gen_error_message("Cannot connect to database")))
	    } else {
		Ok(HttpResponse::InternalServerError().body(gen_error_message(e.to_string().as_str())))
	    }
	}
    }

}

#[post("/")]
async fn insert(new_movie: web::Form<Movie>) -> Result<impl Responder, HttpError> {
    println!("{:?}", new_movie);

    let client = Client::default();
    match client.post(parse_database_url()).send_json(&new_movie).await {
	Ok(mut res) => match res.status().is_success() {
	    true => {
		match render(true, "Film ajouté").await {
		    Ok(page) => return Ok(HttpResponse::Ok()
				   .content_type(ContentType::plaintext())
				   .insert_header(("Content-Type", "text/html"))
				   .body(page)),
		    Err(e) => return Ok(HttpResponse::InternalServerError().body(e.to_string()))
		}
	    }
	    false => match render(false, "Erreur lors de l'ajout").await {
		    Ok(page) => return Ok(HttpResponse::Ok()
				   .content_type(ContentType::plaintext())
				   .insert_header(("Content-Type", "text/html"))
				   .body(page)),
		    Err(e) => return Ok(HttpResponse::InternalServerError().body(e.to_string()))
		}
	}
	Err(e) => return Ok(HttpResponse::InternalServerError().body(e.to_string()))
    };
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));

    let port: u16 = match env::var("PORT") {
	Ok(port) => port.parse().unwrap(),
	Err(_) => panic!("You must set PORT env var")
    };
    
    let database_url = match env::var("DATABASE_URL") {
	Ok(database_url) => database_url,
	Err(_) => panic!("You must set DATABASE_URL env var")
    };

    info!("App started on port {} using DATABASE_URL {}", port, database_url);
    
    HttpServer::new(|| {
        App::new()
	    .wrap(Logger::new("%a \"%r\" %s b  %T"))
            .service(index)
            .service(insert)
    })
	.bind(("0.0.0.0", port))?
	.run()
	.await
}
