use aide::{axum::ApiRouter, openapi::OpenApi};
use axum::{extract::DefaultBodyLimit, Extension};
use hyper::{
    header::{ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_LENGTH, DATE, SET_COOKIE, VARY},
    Method,
};
use std::{
    net::{Ipv4Addr, SocketAddr},
    sync::Arc,
    time::Duration,
};
use tower_http::{compression::CompressionLayer, cors::CorsLayer, timeout::TimeoutLayer};
use tracing::info;
use tracing_subscriber::fmt::SubscriberBuilder;

pub mod config;
pub mod db;
pub mod docs;
pub mod extractors;
mod frontend;
pub mod stars;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    SubscriberBuilder::default().pretty().init();
    info!("running migrations! please wait...");
    sqlx::migrate!("./migrations").run(db::pool().await).await?;
    info!("migrations done! 😎");
    start_server().await?;
    Ok(())
}

async fn generate_server() -> Result<axum::Router, Box<dyn std::error::Error>> {
    aide::gen::on_error(|error| panic!("{}", error));
    aide::gen::extract_schemas(true);
    let mut open_api = OpenApi::default();
    aide::gen::on_error(|error| panic!("{}", error));
    Ok(ApiRouter::new()
        .nest_api_service("/api/v1/stars", stars::routes())
        .nest_api_service("/docs", docs::routes())
        .nest_api_service("/", frontend::routes())
        .finish_api_with(&mut open_api, |docs| {
            docs.title("starchart")
                .summary("now you can discover a star!")
                .description("disclaimer: this website is still in beta! your stars might change at any point!")
                .contact(aide::openapi::Contact {
                    name: Some("zoe".into()),
                    url: Some("https://zoe.kittycat.homes".into()),
                    ..Default::default()
                })
                .version("0.1.0")
        })
        .layer(Extension(Arc::new(open_api)))
        .layer(TimeoutLayer::new(Duration::from_secs(60)))
        .layer(DefaultBodyLimit::max(2000))
        .layer(
            CorsLayer::new()
                .allow_methods([Method::POST, Method::GET, Method::PUT, Method::PATCH])
                .allow_origin([
                    #[cfg(debug_assertions)]
                    "localhost".parse()?,
                    #[cfg(debug_assertions)]
                    "http://localhost:8080".parse()?,
                    #[cfg(debug_assertions)]
                    "http://127.0.0.1:8080".parse()?,
                    crate::config::CONFIG.server_url.parse()?,
                ])
                .allow_headers([
                    ACCESS_CONTROL_ALLOW_ORIGIN,
                    SET_COOKIE,
                    VARY,
                    DATE,
                    CONTENT_LENGTH,
                ])
                .expose_headers([
                    ACCESS_CONTROL_ALLOW_ORIGIN,
                    SET_COOKIE,
                    VARY,
                    DATE,
                    CONTENT_LENGTH,
                ])
                .allow_credentials(true),
        )
        .layer(
            CompressionLayer::new()
                .gzip(true)
                // we have as much cpu as we want really
                // since rust is kinda overkill anyway
                .quality(tower_http::CompressionLevel::Best),
        ))
}

async fn start_server() -> Result<(), Box<dyn std::error::Error>> {
    let app = generate_server().await?;
    let addr = SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7056);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await?;
    Ok(())
}
