use axum::http::{header, HeaderValue};
use tower::ServiceBuilder;
use tower_http::services::fs::ServeFileSystemResponseBody;
use tower_http::services::{ServeDir, ServeFile};
use tower_http::set_header::SetResponseHeaderLayer;
use tower_http::trace::TraceLayer;
use tracing_subscriber::EnvFilter;

use photos::config::Config;
use photos::state::AppState;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter(
            EnvFilter::try_from_default_env().unwrap_or_else(|_| "info,sqlx=warn".into()),
        )
        .init();

    let config = Config::from_env()?;
    let state = AppState::new(config).await?;

    let static_dir = state.config.static_dir.clone();
    let static_root = std::path::Path::new(&static_dir);
    // Hashed assets cache forever — except 404s, which would outlive the next deploy.
    let assets = ServiceBuilder::new()
        .map_response(|mut response: axum::http::Response<ServeFileSystemResponseBody>| {
            if response.status().is_success() {
                response.headers_mut().insert(
                    header::CACHE_CONTROL,
                    HeaderValue::from_static("public, max-age=31536000, immutable"),
                );
            }
            response
        })
        .service(ServeDir::new(static_root.join("assets")));
    // index.html revalidates every load (it names the asset hashes);
    // .fallback (not .not_found_service) so SPA routes get it with a 200.
    let spa = ServiceBuilder::new()
        .layer(SetResponseHeaderLayer::overriding(
            header::CACHE_CONTROL,
            HeaderValue::from_static("no-cache"),
        ))
        .service(ServeDir::new(static_root).fallback(ServeFile::new(static_root.join("index.html"))));

    let app = photos::routes::router(&state)
        .nest_service("/assets", assets)
        .fallback_service(spa)
        .layer(TraceLayer::new_for_http())
        .with_state(state.clone());

    let listener = tokio::net::TcpListener::bind(&state.config.bind_addr).await?;
    tracing::info!("listening on http://{}", state.config.bind_addr);
    axum::serve(listener, app).await?;
    Ok(())
}
