use std::net::SocketAddr;
use askama_axum::Template;
use axum::{
    middleware, response::{Html, IntoResponse, Response}, routing::{get, get_service}, Extension, Router
};
use http::{Request, StatusCode};
use serde::{Deserialize, Serialize};
use sqlx::{prelude::FromRow, sqlite::SqlitePoolOptions, SqlitePool};
use sqlx::migrate::Migrator;
use tower_http::services::ServeDir;

mod error_handling;
mod google_oauth;
mod middlewares;
mod oauth;
mod routes;

use error_handling::AppError;
use google_oauth::*;
use middlewares::{check_auth, inject_user_data};
use oauth::{login, logout, google_auth_return};
use routes::*;

struct HtmlTemplate<T>(T);

impl<T> IntoResponse for HtmlTemplate<T>
where
    T: Template,
{
    fn into_response(self) -> Response {
        match self.0.render() {
            Ok(html) => Html(html).into_response(),
            Err(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to render template. Error: {}", err),
            )
                .into_response(),
        }
    }
}

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
    logged_in: bool,
    name: String,
}

#[derive(Clone)]
pub struct AppState {
    pub db_pool: SqlitePool,
}

#[derive(Default, Clone, Debug, FromRow, Serialize, Deserialize)]
pub struct UserData {
    #[allow(dead_code)]
    pub id: i64,
    pub email: String,
    pub name: String,
    pub family_name: String,
    pub given_name: String,
}

#[tokio::main]
async fn main() {
    // initialize tracing
    tracing_subscriber::fmt::init();

    let db_pool = SqlitePoolOptions::new()
        .max_connections(5)
        .connect("sqlite://db/db.sqlite3")
        .await;
    
    let app_state = AppState {db_pool: db_pool.expect("Failed to get db_pool") };

    static MIGRATOR: Migrator = sqlx::migrate!();

    MIGRATOR
        .run(&app_state.db_pool)
        .await
        .expect("Failed to run migrations");

    let user_data: Option<UserData> = None;

    // build our application with some routes
    let app = Router::new()
        //Routes that require authentication
        .route("/profile", get(profile))
        .route("/useradmin", get(useradmin))
        .route("/users/:user_id", get(user_profile))
        .route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), check_auth))

        //Routes that don't require authentication
        .nest_service("/assets", ServeDir::new("templates/assets")
            .fallback(get_service(ServeDir::new("templates/assets"))))
        .route("/", get(index))
        .route("/login", get(login))
        .route("/logout", get(logout))
        .route("/google_auth", get(google_auth))
        .route("/google_auth_return", get(google_auth_return))
        .route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), inject_user_data))
        .with_state(app_state.db_pool)
        .layer(Extension(user_data))
        ;

    // run it
    let addr = SocketAddr::from(([0, 0, 0, 0], 40192));
    tracing::debug!("listening on {}", addr);
    axum_server::bind(addr)
        .serve(app.into_make_service())
        .await
        .unwrap();

}

async fn index<T>(
    Extension(user_data): Extension<Option<UserData>>,
    _request: Request<T>,
) -> impl IntoResponse {
    let user_email = user_data.map(|s| s.name);
    let logged_in = user_email.is_some();
    let name = user_email.unwrap_or_default();

    let template = IndexTemplate { logged_in, name};
    HtmlTemplate(template)
}
