use axum::{
    extract::State,
    http::{HeaderMap, StatusCode},
    response::Json,
    routing::{get, post},
    Router,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{postgres::PgPoolOptions, PgPool, Row};
use tower_http::cors::{Any, CorsLayer};
use uuid::Uuid;

const ADMIN_PASSWORD: &str = "2123";

#[derive(Clone)]
struct AppState {
    db: PgPool,
}

#[derive(Deserialize)]
struct SubmitRequest {
    name: String,
    alcohol: Option<String>,
    food: Option<String>,
    with_partner: bool,
    partner_name: Option<String>,
    partner_alcohol: Option<String>,
    partner_food: Option<String>,
}

#[derive(Serialize)]
struct SubmitResponse {
    id: Uuid,
}

#[derive(Serialize)]
struct Response {
    id: Uuid,
    name: String,
    alcohol: Option<String>,
    food: Option<String>,
    with_partner: bool,
    partner_name: Option<String>,
    partner_alcohol: Option<String>,
    partner_food: Option<String>,
    created_at: DateTime<Utc>,
}

async fn submit(
    State(state): State<AppState>,
    Json(body): Json<SubmitRequest>,
) -> Result<Json<SubmitResponse>, StatusCode> {
    let row = sqlx::query(
        "INSERT INTO responses (name, alcohol, food, with_partner, partner_name, partner_alcohol, partner_food)
         VALUES ($1, $2, $3, $4, $5, $6, $7)
         RETURNING id",
    )
    .bind(&body.name)
    .bind(&body.alcohol)
    .bind(&body.food)
    .bind(body.with_partner)
    .bind(&body.partner_name)
    .bind(&body.partner_alcohol)
    .bind(&body.partner_food)
    .fetch_one(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("DB error: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    let id: Uuid = row.get("id");
    Ok(Json(SubmitResponse { id }))
}

async fn admin_responses(
    State(state): State<AppState>,
    headers: HeaderMap,
) -> Result<Json<Vec<Response>>, StatusCode> {
    let password = headers
        .get("x-admin-password")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

    if password != ADMIN_PASSWORD {
        return Err(StatusCode::UNAUTHORIZED);
    }

    let rows = sqlx::query(
        "SELECT id, name, alcohol, food, with_partner, partner_name, partner_alcohol, partner_food, created_at
         FROM responses ORDER BY created_at DESC",
    )
    .fetch_all(&state.db)
    .await
    .map_err(|e| {
        tracing::error!("DB error: {e}");
        StatusCode::INTERNAL_SERVER_ERROR
    })?;

    let responses = rows
        .iter()
        .map(|r| Response {
            id: r.get("id"),
            name: r.get("name"),
            alcohol: r.get("alcohol"),
            food: r.get("food"),
            with_partner: r.get("with_partner"),
            partner_name: r.get("partner_name"),
            partner_alcohol: r.get("partner_alcohol"),
            partner_food: r.get("partner_food"),
            created_at: r.get("created_at"),
        })
        .collect();

    Ok(Json(responses))
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt::init();
    dotenvy::dotenv().ok();

    let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    let port = std::env::var("PORT").unwrap_or_else(|_| "8080".to_string());

    let db = PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await
        .expect("Failed to connect to database");

    sqlx::migrate!()
        .run(&db)
        .await
        .expect("Failed to run migrations");

    let cors = CorsLayer::new()
        .allow_origin(Any)
        .allow_methods(Any)
        .allow_headers(Any);

    let app = Router::new()
        .route("/api/submit", post(submit))
        .route("/api/admin/responses", get(admin_responses))
        .layer(cors)
        .with_state(AppState { db });

    let addr = format!("0.0.0.0:{port}");
    tracing::info!("Listening on {addr}");
    let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
    axum::serve(listener, app).await.unwrap();
}
