mod config;
mod manifest;
mod server;
mod upstream;

use std::{env, error::Error, io, process};

use config::api_bases;
use manifest::Catalog;
use tokio::net::TcpListener;
use tracing::info;

const BIND_START: std::net::SocketAddr =
    std::net::SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), 3111);

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    brk_logger::init_with_default_level(None, "info")?;

    let mut arguments = env::args();
    let _program = arguments.next();
    let Some(api_base) = arguments.next() else {
        usage();
    };
    if arguments.next().is_some() {
        usage();
    }
    let api_bases = api_bases(&api_base).map_err(io::Error::other)?;
    let catalog = Catalog::embedded().map_err(io::Error::other)?;
    let tool_count = catalog.tools().len();
    let app = server::router(api_bases, catalog);
    let (listener, bind) = bind_available(BIND_START).await?;

    info!(%bind, tool_count, "Starting stateless BRK MCP server");
    axum::serve(listener, app).await?;
    Ok(())
}

fn usage() -> ! {
    eprintln!("Usage: brk_mcp <REST_API_URL_OR_HOST>");
    process::exit(2);
}

async fn bind_available(
    start: std::net::SocketAddr,
) -> io::Result<(TcpListener, std::net::SocketAddr)> {
    let last_port = start.port().saturating_add(100);
    let mut last_error = None;
    for port in start.port()..=last_port {
        let mut candidate = start;
        candidate.set_port(port);
        match TcpListener::bind(candidate).await {
            Ok(listener) => return Ok((listener, candidate)),
            Err(error) => last_error = Some(error),
        }
    }
    Err(last_error.unwrap_or_else(|| {
        io::Error::new(
            io::ErrorKind::AddrNotAvailable,
            "no MCP bind port available",
        )
    }))
}
