//! M7.1 smoke service.
//!
//! A standalone Axum binary whose only job is to host the
//! [`compliance_core::auth`] middleware + [`compliance_core::tenant_ctx`]
//! extractor on three endpoints, so `scripts/smoke.sh` can prove the
//! tenant-gating contract end-to-end before any auth-path PR merges.
//!
//! Endpoints:
//! * `GET  /api/v1/health` — public, never authenticated.
//! * `GET  /api/v1/echo`   — protected read; returns the [`TenantContext`].
//! * `POST /api/v1/echo`   — protected write; exercises the `Frozen → 402`
//!   gate on the same handler.
//!
//! Configuration (env):
//! * `KEYCLOAK_URL`   — e.g. `http://localhost:8080`. Required.
//! * `KEYCLOAK_REALM` — e.g. `certifai`. Required.
//! * `SMOKE_PORT`     — defaults to `3010`.

use std::sync::Arc;

use axum::{middleware, routing::get, Extension, Json, Router};
use compliance_core::{
    auth::{require_jwt_auth, require_tenant_status, JwksState},
    tenant_ctx::TenantCtx,
};
use serde::Serialize;
use tokio::sync::RwLock;

#[derive(Serialize)]
struct EchoResponse {
    method: &'static str,
    tenant_id: String,
    tenant_slug: String,
    plan: String,
    status: String,
    products: Vec<String>,
    org_roles: Vec<String>,
    user_id: String,
    user_name: Option<String>,
}

async fn health() -> Json<serde_json::Value> {
    Json(serde_json::json!({ "ok": true }))
}

async fn echo_read(TenantCtx(ctx): TenantCtx) -> Json<EchoResponse> {
    Json(echo(ctx, "GET"))
}

async fn echo_write(TenantCtx(ctx): TenantCtx) -> Json<EchoResponse> {
    Json(echo(ctx, "POST"))
}

fn echo(ctx: compliance_core::TenantContext, method: &'static str) -> EchoResponse {
    EchoResponse {
        method,
        tenant_id: ctx.tenant_id,
        tenant_slug: ctx.tenant_slug,
        plan: ctx.plan,
        status: ctx.status.to_string(),
        products: ctx.products,
        org_roles: ctx.org_roles.iter().map(|r| format!("{r:?}")).collect(),
        user_id: ctx.user_id,
        user_name: ctx.user_name,
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    let kc_url = std::env::var("KEYCLOAK_URL")
        .map_err(|_| "KEYCLOAK_URL is required (e.g. http://localhost:8080)")?;
    let kc_realm = std::env::var("KEYCLOAK_REALM")
        .map_err(|_| "KEYCLOAK_REALM is required (e.g. certifai)")?;
    let port: u16 = std::env::var("SMOKE_PORT")
        .ok()
        .and_then(|s| s.parse().ok())
        .unwrap_or(3010);

    let jwks_url = format!("{kc_url}/realms/{kc_realm}/protocol/openid-connect/certs");
    let jwks_state = JwksState {
        jwks: Arc::new(RwLock::new(None)),
        jwks_url: jwks_url.clone(),
    };

    // Layers execute outermost-first. The Extension must be registered
    // before `require_jwt_auth` so the middleware can read JwksState; the
    // status gate must run after JWT so `TenantContext` is in extensions.
    let app = Router::new()
        .route("/api/v1/health", get(health))
        .route("/api/v1/echo", get(echo_read).post(echo_write))
        .layer(middleware::from_fn(require_tenant_status))
        .layer(middleware::from_fn(require_jwt_auth))
        .layer(Extension(jwks_state));

    let addr = format!("0.0.0.0:{port}");
    let listener = tokio::net::TcpListener::bind(&addr).await?;
    tracing::info!(
        port,
        jwks = %jwks_url,
        "compliance-smoke listening — try `scripts/smoke.sh`"
    );
    axum::serve(listener, app).await?;
    Ok(())
}
