#[macro_use]
extern crate dotenv_codegen;
extern crate dotenv;

use dotenv::dotenv;
use tokio::time::sleep;

use std::{env, error::Error, fs, net::Ipv4Addr, net::Ipv6Addr, thread, time::Duration, sync::{Mutex, Arc, atomic::{AtomicBool, Ordering}}};

use chrono::Local;

use actix_web::{middleware::Logger, web, App, HttpServer, Result};
use serde::{Deserialize, Serialize};
use sqlx::{PgPool, Pool, Postgres, Connection};
use utoipa::OpenApi;
use utoipa_swagger_ui::{Config, SwaggerUi, Url};

mod db;
mod v2;
mod v3;

macro_rules! api_base {
    () => {
        "/api"
    };
}

macro_rules! api_base_2 {
    () => {
        "/api/v2"
    };
}

macro_rules! api_base_3 {
    () => {
        "/api/v3"
    };
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct ConfigToml {
    auth: AuthenticationTokens,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct AuthenticationTokens {
    moderator: String,
    admin: String
}

pub struct AppState {
    db: Pool<Postgres>,
    auth_tokens: AuthenticationTokens,
}

async fn postgres_watchdog(pool: PgPool, is_alive: Arc<AtomicBool>, shutdown: Arc<AtomicBool>) {
    loop {
        if shutdown.load(Ordering::Relaxed) {
            break;
        }
        let start = Local::now();

        let mut conn = match pool.acquire().await {
            Ok(data) => data,
            Err(_) => break,
        };

        match conn.ping().await {
            Ok(_) => {println!("Pinged DB Server at {}", Local::now().format("%H:%M:%S"))},
            Err(_) => {println!("Error pinging Server"); break;},
        };

        let passed = (Local::now() - start).to_std().expect(&format!("Unable to get Time Difference for '{}' and '{}'", start, Local::now()));
        
        sleep(Duration::from_secs(15) - passed).await;
    }
    is_alive.store(false, Ordering::Relaxed);
}

#[actix_web::main]
async fn main() -> Result<()> {
    env_logger::init();
    dotenv().ok();

    let shutdown: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));

    let shutdown_clone = Arc::clone(&shutdown);
    ctrlc::set_handler(move || {
        eprintln!("Ctrl-C received");
        shutdown_clone.store(true, Ordering::Relaxed)
    })
    .expect("Error setting Ctrl-C handler");

    let toml_str = fs::read_to_string("config.toml").expect("Failed to read config.toml");

    let config: ConfigToml = toml::from_str(&toml_str).expect("Failed to parse config.toml");

    println!("DATABASE_URL: {}", env::var("DATABASE_URL").unwrap()); // DBEUG

    #[derive(OpenApi)]
    #[openapi(
        paths(
            v2::empire_ethics,
            v2::empires,
            v2::ethics,
            v2::game_groups,
            v2::games,
            v2::portrait_groups,
            v2::portraits
        ),
        components(schemas(
            v2::schemas::PortraitGroup,
            v2::schemas::Portrait,
            v2::schemas::Ethic,
            v2::schemas::Empire,
            v2::schemas::EmpireEthic,
            v2::schemas::Game,
            v2::schemas::GameGroup
        ))
    )]
    struct ApiDocV2;

    #[derive(OpenApi)]
    #[openapi(
        paths(
            v3::full_view_data,
            v3::auth,
            v3::list_games,
            v3::get_game_data,
            v3::create_game,
            v3::delete_game
        ), 
        components(schemas(
            v3::schemas::AuthParamsOptional,
            v3::schemas::AuthParams,
            v3::schemas::AuthReturn,
            v3::schemas::GetGameParam,
            v3::schemas::PostGameParams,
            v3::schemas::DeleteGameParam,
            v3::schemas::FullViewData,
            v3::schemas::Ethic,
            v3::schemas::EmpireEthic,
            v3::schemas::ChellarisGame,
            v3::schemas::ChellarisGameLite,
            v3::schemas::Species,
            v3::schemas::ChellarisGameGroup,
            v3::schemas::Portrait,
            v3::schemas::ChellarisEmpire
        ))
    )]
    struct ApiDocV3;

    let openapi_urls = vec![
        Url::new("v2", concat!(api_base_2!(), "/openapi.json")),
        Url::new("v3", concat!(api_base_3!(), "/openapi.json")),
    ];

    let is_alive: Arc<AtomicBool> = Arc::new(AtomicBool::new(true));

    loop {
        let db_auth_tokens = config.auth.clone();
        let pool = PgPool::connect(dotenv!("DATABASE_URL")).await.unwrap();
        let pool_copy = pool.clone();

        let swagger_config = Config::new(openapi_urls.clone());

        let mut openapi_v2 = ApiDocV2::openapi();
        openapi_v2.info.title = "Chellaris Rust API".to_string();
    
        let mut openapi_v3 = ApiDocV3::openapi();
        openapi_v3.info.title = "Chellaris Rust API".to_string();

        let server = HttpServer::new(move || {
            App::new()
                .app_data(web::Data::new(AppState { db: pool.clone(), auth_tokens: db_auth_tokens.clone() }))
                .wrap(Logger::default())
                // API v2 Endpoints
                .service(v2::empire_ethics)
                .service(v2::empires)
                .service(v2::ethics)
                .service(v2::game_groups)
                .service(v2::games)
                .service(v2::portrait_groups)
                .service(v2::portraits)
                // API v3 Endpoints
                .service(v3::full_view_data)
                .service(v3::auth)
                .service(v3::list_games)
                .service(v3::get_game_data)
                .service(v3::create_game)
                .service(v3::delete_game)
                // Swagger UI
                .service(
                    SwaggerUi::new(concat!(api_base!(), "/swagger/{_:.*}"))
                        .urls(vec![
                            (
                                Url::new("v2", concat!(api_base_2!(), "/openapi.json")),
                                openapi_v2.clone(),
                            ),
                            (
                                Url::new("v3", concat!(api_base_3!(), "/openapi.json")),
                                openapi_v3.clone(),
                            ),
                        ])
                        .config(swagger_config.clone()),
                )
        })
        .bind((Ipv6Addr::UNSPECIFIED, 8080)).expect("Port or IP already occupied")
        .run();

        let server_thread = tokio::spawn(async {
            println!("Awaiting server");
            let _ = server.await;
            println!("Stopped awaiting server");
        });

        println!("Started Serving API on: ");
        println!(" -> http://[{}]:{}", Ipv6Addr::UNSPECIFIED, 8080);
        println!(" -> http://{}:{}", Ipv4Addr::UNSPECIFIED, 8080);

        let is_alive_clone = Arc::clone(&is_alive);
        let shutdown_clone = Arc::clone(&shutdown);
        let _ = tokio::spawn(async move { postgres_watchdog(pool_copy, is_alive_clone, shutdown_clone).await });
        
        //watchdog_thread.await;

        while is_alive.load(Ordering::Relaxed) {
            let thread = tokio::spawn(async {
                thread::sleep(Duration::from_millis(100));
            });

            let _ = thread.await;
        }

        if shutdown.load(Ordering::Relaxed) {
            break;
        }

        eprintln!("Connection died, restarting Server");
        server_thread.abort();
    }

    Ok(())
}