mod answer;
mod api;
mod pg_store;
mod question;
mod question_tag;
mod tag;
use axum::{
    extract::{Path, Query, State},
    http::{StatusCode, Uri},
    response::{IntoResponse, Response},
    routing::{delete, get, post, put},
    Form, Json, Router,
};
use serde::{Deserialize, Serialize};
use sqlx::{
    self,
    postgres::{PgPool, PgPoolOptions, PgRow, Postgres},
    Pool, Row,
};
use std::{env::var, fs::File, io::Read, net::SocketAddr, path::PathBuf, sync::Arc};
use tokio::sync::RwLock;

// generic handler for any not supported route/method combination
async fn handler_404() -> Response {
    (StatusCode::NOT_FOUND, "404 Not Found").into_response()
}

// generic handler to serve static for non-api routes
async fn serve_file(uri: Uri) -> impl IntoResponse {
    let mut path_buf: PathBuf =
        PathBuf::from("./src/static/").join(uri.path().trim_start_matches('/'));
    if path_buf.is_dir() {
        path_buf.push("index.html")
    }
    match File::open(path_buf) {
        Ok(mut file) => {
            let mut contents = Vec::new();
            match file.read_to_end(&mut contents) {
                Ok(_) => axum::response::Response::builder()
                    .status(StatusCode::OK)
                    .body(axum::body::Body::from(contents))
                    .unwrap(),
                Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Server Error").into_response(),
            }
        }
        Err(_) => handler_404().await,
    }
}

//We could create/select a particular database with the URL path, but this would complicate setup, add another env, and provides little value
fn db_string() -> String {
    format!(
        "postgres://{}:{}@{}:5432",
        var("POSTGRES_USER").unwrap(),
        var("POSTGRES_PASSWORD").unwrap(),
        var("POSTGRES_HOST").unwrap(),
    )
}

#[tokio::main]
async fn main() {
    let db_url: String = db_string();
    let store: Arc<RwLock<pg_store::Store>> =
        Arc::new(RwLock::new(pg_store::Store::new(&db_url).await));
    let ip: SocketAddr = SocketAddr::new([127, 0, 0, 1].into(), 3000);
    let listener: tokio::net::TcpListener = tokio::net::TcpListener::bind(ip).await.unwrap();
    let apis = Router::new()
        .route("/question/:id", get(api::read_question))
        .route("/questions", get(api::read_questions))
        .route("/question", post(api::create_question))
        .route("/question", put(api::update_question))
        .route("/question/:id", delete(api::delete_question))
        .route("/answer", post(api::create_answer))
        .with_state(store);
    let app = Router::new()
        .nest("/api/v1", apis)
        .route("/", get(serve_file))
        .route("/*path", get(serve_file))
        .fallback(handler_404);

    axum::serve(listener, app).await.unwrap();
}
