use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;

use clap::{Args, Parser, Subcommand};
use tidal_server::config::load_schema;
use tidal_server::error::{Result, ServerError};
use tidal_server::router::build_router;
use tidal_server::state::ServerState;
use tidaldb::TidalDb;

#[derive(Parser)]
#[command(version, about = "HTTP wrapper for tidalDB")]
struct Cli {
    #[command(subcommand)]
    mode: Command,
}

#[derive(Subcommand)]
enum Command {
    #[command(about = "Run a single-node server wrapping one tidalDB instance")]
    Standalone(StandaloneArgs),
}

#[derive(Args)]
struct StandaloneArgs {
    #[arg(long, default_value = "127.0.0.1:9400")]
    listen: String,
    #[arg(long)]
    schema: Option<PathBuf>,
    #[arg(long)]
    data_dir: Option<PathBuf>,
    #[arg(
        long,
        help = "Bind address for Prometheus /metrics endpoint (e.g. 127.0.0.1:9091)"
    )]
    metrics: Option<String>,
}

#[tokio::main]
async fn main() {
    if let Err(err) = run().await {
        eprintln!("error: {err}");
        std::process::exit(1);
    }
}

async fn run() -> Result<()> {
    let cli = Cli::parse();
    init_tracing();

    match cli.mode {
        Command::Standalone(args) => run_standalone(args).await,
    }
}

fn init_tracing() {
    let env_filter = std::env::var("TIDAL_SERVER_LOG").unwrap_or_else(|_| "info".into());
    let _ = tracing_subscriber::fmt()
        .with_env_filter(env_filter)
        .try_init();
}

async fn run_standalone(args: StandaloneArgs) -> Result<()> {
    let schema = load_schema(args.schema.as_deref())?;

    let mut builder = TidalDb::builder().with_schema(schema.clone());
    if let Some(dir) = args.data_dir {
        builder = builder.with_data_dir(dir);
    } else {
        builder = builder.ephemeral();
    }
    if let Some(ref addr) = args.metrics {
        builder = builder.enable_metrics(addr);
    }

    let db = builder.open()?;
    if let Some(addr) = db.metrics_addr() {
        tracing::info!("metrics endpoint listening on http://{addr}/metrics");
    }
    let state = ServerState::new(db);

    let api_key = read_api_key();
    serve(state, &args.listen, api_key).await
}

/// Read the API key from the environment.
///
/// If `TIDAL_API_KEY` is not set, all requests are accepted without
/// authentication. This is appropriate for local development but should
/// never be used in production. A startup warning is emitted.
fn read_api_key() -> Option<Arc<str>> {
    match std::env::var("TIDAL_API_KEY") {
        Ok(key) if !key.is_empty() => Some(Arc::from(key.as_str())),
        _ => {
            tracing::warn!(
                "TIDAL_API_KEY is not set — all endpoints are unauthenticated. \
                 Set this variable before exposing the server to any network."
            );
            None
        }
    }
}

async fn serve(state: ServerState, addr: &str, api_key: Option<Arc<str>>) -> Result<()> {
    let socket: SocketAddr = addr
        .parse()
        .map_err(|e| ServerError::BadRequest(format!("invalid addr: {e}")))?;

    let listener = tokio::net::TcpListener::bind(socket).await?;
    let actual = listener.local_addr()?;
    tracing::info!("listening on http://{actual}");

    let state = Arc::new(state);
    let shutdown_state = state.clone();

    axum::serve(listener, build_router(state, api_key))
        .with_graceful_shutdown(shutdown_signal(shutdown_state))
        .await?;
    Ok(())
}

async fn shutdown_signal(state: Arc<ServerState>) {
    // SIGTERM is Unix-only; on other platforms we fall back to ctrl-c alone.
    #[cfg(unix)]
    let sigterm = async {
        use tokio::signal::unix::{SignalKind, signal};
        match signal(SignalKind::terminate()) {
            Ok(mut stream) => {
                stream.recv().await;
            }
            Err(err) => {
                tracing::warn!("SIGTERM handler registration failed: {err}");
                // If we cannot register SIGTERM, park this branch so the
                // select falls back to ctrl_c exclusively.
                std::future::pending::<()>().await;
            }
        }
    };
    #[cfg(not(unix))]
    let sigterm = std::future::pending::<()>();

    tokio::select! {
        result = tokio::signal::ctrl_c() => {
            if let Err(err) = result {
                tracing::warn!("ctrl-c handler error: {err}");
            }
        }
        _ = sigterm => {}
    }

    // Flip readiness BEFORE axum starts draining
    state.set_shutting_down();
    tracing::info!("readiness flipped to not-ready, draining in-flight requests");
}
