#[cfg(feature = "actix-files")]
use actix_files::Files;

use actix_web::{middleware, web, App, HttpServer};

use handlebars::Handlebars;

mod config;
use config::{read_dsn, Config};

mod pages;
use pages::atom::games::atom_games;
use pages::atom::turns::atom_turns;
use pages::cookies_policy::{cookies_policy, post_accept_cookies, post_remove_cookies};
use pages::index::index;
use pages::log_in::{get_log_in, post_log_in, post_log_in_mastodon};
use pages::log_out::log_out;
use pages::my::my;
use pages::query_reset_game_pwd::post_query_reset_game_pwd;
use pages::query_reset_game_pwd::query_reset_game_pwd;
use pages::reset_game_pwd::get_reset_game_pwd;
use pages::reset_game_pwd::post_reset_game_pwd;
use pages::slow_game::{
    post_delegate, post_join_game, post_leave_game, post_query_delegate, post_revoke_delegate,
    slow_game,
};
use pages::slow_games::slow_games;
use pages::templates;
use pages::well_known;
use pages::{DataBaseRo, DataBaseRw, WebData};

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

    let args = clap::Command::new("FreeOrion Test Web")
        .version("0.1.0")
        .author("O01eg <o01eg@yandex.ru>")
        .arg(
            clap::Arg::new("config")
                .short('c')
                .long("config")
                .value_name("CONFIG")
                .help("File with configuration")
                .action(clap::ArgAction::Set)
                .required(true),
        )
        .get_matches();

    let config = Config::read(
        args.get_one::<String>("config")
            .expect("Mandatory option config"),
    )?;

    let Config {
        http,
        dsn_ro_path,
        dsn_rw_path,
        dsn_conn,
        cache_capacity,
        cache_duration_sec,
        base_proto,
        base_domain,
        cookies_key_base64,
        set_cookie_domain,
        mail,
        xmpp_url,
        freeorion_version,
        #[cfg(feature = "actix-files")]
        public,
    } = config;

    use base64::Engine;
    let cookies_key = if let Some(c) = cookies_key_base64 {
        match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(c) {
            Ok(b) => {
                if b.len() < 64 {
                    log::error!("Small key for cookies");
                    return Err(std::io::Error::new(std::io::ErrorKind::Other, "Small key"));
                } else {
                    actix_web::cookie::Key::from(&b)
                }
            }
            Err(e) => {
                log::error!("Incorrect key for cookies {}", e);
                return Err(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    "Incorrect key",
                ));
            }
        }
    } else {
        log::error!("Missing key for cookies");
        if let Some(nc) = actix_web::cookie::Key::try_generate() {
            log::info!(
                "Put line `cookies_key_base64 = \"{}\"` to config",
                base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(nc.master())
            );
            return Err(std::io::Error::new(
                std::io::ErrorKind::Other,
                "Missing key",
            ));
        } else {
            log::error!("Cann't generate key for cookies");
            return Err(std::io::Error::new(
                std::io::ErrorKind::Other,
                "Cann't generate key",
            ));
        }
    };

    let pg_ro_config = read_dsn(dsn_ro_path)?;
    let pg_rw_config = read_dsn(dsn_rw_path)?;
    let mgr_ro = deadpool_postgres::Manager::from_config(
        pg_ro_config,
        tokio_postgres::NoTls,
        deadpool_postgres::ManagerConfig {
            recycling_method: deadpool_postgres::RecyclingMethod::Fast,
        },
    );
    let mgr_rw = deadpool_postgres::Manager::from_config(
        pg_rw_config,
        tokio_postgres::NoTls,
        deadpool_postgres::ManagerConfig {
            recycling_method: deadpool_postgres::RecyclingMethod::Fast,
        },
    );

    let pool_ro = deadpool_postgres::Pool::builder(mgr_ro)
        .max_size(dsn_conn)
        .build()
        .expect("Postgresql RO pool");

    let pool_rw = deadpool_postgres::Pool::builder(mgr_rw)
        .max_size(dsn_conn)
        .build()
        .expect("Postgresql RW pool");

    let mut handlebars = Handlebars::new();
    handlebars.set_strict_mode(true);
    handlebars
        .register_template_string(
            templates::sub::FOOTER,
            include_str!("templates/sub/footer.html"),
        )
        .expect("footer sub template");
    handlebars
        .register_template_string(
            templates::sub::HEADER,
            include_str!("templates/sub/header.html"),
        )
        .expect("header sub template");
    handlebars
        .register_template_string(templates::INDEX, include_str!("templates/index.html"))
        .expect("index template");
    handlebars
        .register_template_string(templates::MY, include_str!("templates/my.html"))
        .expect("my template");
    handlebars
        .register_template_string(
            templates::SLOW_GAMES,
            include_str!("templates/slow-games.html"),
        )
        .expect("slow games template");
    handlebars
        .register_template_string(
            templates::RESET_GAME_PWD,
            include_str!("templates/reset-game-pwd.html"),
        )
        .expect("reset game pwd template");
    handlebars
        .register_template_string(
            templates::QUERY_RESET_GAME_PWD,
            include_str!("templates/query-reset-game-pwd.html"),
        )
        .expect("query reset game pwd template");
    handlebars
        .register_template_string(
            templates::COOKIES_POLICY,
            include_str!("templates/cookies-policy.html"),
        )
        .expect("cookies policy template");
    handlebars
        .register_template_string(templates::LOG_IN, include_str!("templates/login.html"))
        .expect("cookies policy template");

    let mut handlebars_xml = Handlebars::new();
    handlebars_xml.set_strict_mode(true);
    handlebars_xml.register_escape_fn(handlebars::no_escape);
    handlebars_xml
        .register_template_string(templates::ATOM_GAMES, include_str!("templates/games.xml"))
        .expect("atom games template");
    handlebars_xml
        .register_template_string(templates::ATOM_TURNS, include_str!("templates/turns.xml"))
        .expect("atom games template");
    // put it here to include direct HTML notes
    handlebars_xml
        .register_template_string(
            templates::SLOW_GAME,
            include_str!("templates/slow-game.html"),
        )
        .expect("slow game template");
    handlebars_xml
        .register_template_string(
            templates::sub::FOOTER,
            include_str!("templates/sub/footer.html"),
        )
        .expect("footer sub template");
    handlebars_xml
        .register_template_string(
            templates::sub::HEADER,
            include_str!("templates/sub/header.html"),
        )
        .expect("header sub template");

    let smtp_credentials =
        lettre::transport::smtp::authentication::Credentials::new(mail.login, mail.passwd);

    let data_ref = web::Data::new(WebData {
        handlebars,
        handlebars_xml,
        cache_reset_game_pwd: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
        cache_query_reset_game_pwd: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(
            cache_capacity,
        )),
        cache_login: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
        cache_join_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
        cache_leave_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
        cache_query_delegation_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(
            cache_capacity,
        )),
        cache_revoke_delegation_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(
            cache_capacity,
        )),
        cache_delegation_game: tokio::sync::Mutex::new(ttl_cache::TtlCache::new(cache_capacity)),
        cache_duration_sec,
        base_proto,
        base_domain,
        mailer: lettre::AsyncSmtpTransport::<lettre::Tokio1Executor>::relay(&mail.server)
            .expect("Mail relay server")
            .credentials(smtp_credentials)
            .build(),
        mailer_from: lettre::message::Mailbox::new(
            None,
            mail.from
                .parse::<lettre::address::Address>()
                .expect("Mail from address"),
        ),
        xmpp_url,
        cookies_key,
        set_cookie_domain,
        freeorion_version,
    });

    let data_ref_ro = web::Data::new(DataBaseRo(pool_ro));
    let data_ref_rw = web::Data::new(DataBaseRw(pool_rw));

    #[cfg(feature = "non-proxied")]
    log::info!("Listen {}", http);

    HttpServer::new(move || {
        #[cfg(feature = "non-proxied")]
        let logger = middleware::Logger::default();

        #[cfg(not(feature = "non-proxied"))]
        let logger =
            middleware::Logger::new("%{r}a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T");

        #[allow(clippy::let_and_return)]
        let app = App::new()
            // Enable the logger.
            .wrap(logger)
            .app_data(data_ref.clone())
            .app_data(data_ref_ro.clone())
            .app_data(data_ref_rw.clone())
            .route("/", web::get().to(index))
            .route("/index.html", web::get().to(index))
            .route("/my.html", web::get().to(my))
            .route("/slow-games.html", web::get().to(slow_games))
            .route("/cookies-policy.html", web::get().to(cookies_policy))
            .route("/login.html", web::get().to(get_log_in))
            .route("/accept-cookies.do", web::post().to(post_accept_cookies))
            .route("/remove-cookies.do", web::post().to(post_remove_cookies))
            .route("/logout.do", web::post().to(log_out))
            .route("/login.do", web::post().to(post_log_in))
            .route("/login-mastodon.do", web::post().to(post_log_in_mastodon))
            .route("/join-game.do", web::post().to(post_join_game))
            .route("/leave-game.do", web::post().to(post_leave_game))
            .route("/revoke-delegate.do", web::post().to(post_revoke_delegate))
            .route("/query-delegate.do", web::post().to(post_query_delegate))
            .route("/delegate.do", web::post().to(post_delegate))
            .route(
                "/query-reset-game-pwd.html",
                web::get().to(query_reset_game_pwd),
            )
            .route(
                "/.well-known/host-meta",
                web::get().to(well_known::host_meta),
            )
            .route("/nodeinfo/2.0", web::get().to(well_known::nodeinfo20))
            .route("/.well-known/nodeinfo", web::get().to(well_known::nodeinfo))
            .service(post_query_reset_game_pwd)
            .service(get_reset_game_pwd)
            .service(post_reset_game_pwd)
            .service(slow_game)
            .service(atom_games)
            .service(atom_turns)
            .service(well_known::webfinger);

        // Serve a tree of static files at the web root and specify the index file.
        // Note that the root path should always be defined as the last item. The paths are
        // resolved in the order they are defined. If this would be placed before the `/images`
        // path then the service for the static images would never be reached.
        #[cfg(feature = "actix-files")]
        let app = app.service(Files::new("/static/", &public));

        app
    })
    .bind(http)?
    .run()
    .await
}