mod ingest;
mod routes;
mod watcher;

use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;

use axum::Router;
use axum::extract::DefaultBodyLimit;
use tokio::net::TcpListener;
use tower_http::cors::CorsLayer;
use tower_http::trace::TraceLayer;

use second_brain_core::embedding::Embedder;
use second_brain_core::kuzu_store::KuzuStore;
use second_brain_core::machine::MachineIdentity;

pub struct AppState {
    pub store: Arc<KuzuStore>,
    pub embedder: Option<Arc<Embedder>>,
    pub machine: MachineIdentity,
}

#[tokio::main]
async fn main() {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::from_default_env()
                .add_directive("second_brain=info".parse().unwrap())
                .add_directive("tower_http=debug".parse().unwrap()),
        )
        .init();

    let db_path = std::env::var("SECOND_BRAIN_DB")
        .map(PathBuf::from)
        .unwrap_or_else(|_| {
            let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
            PathBuf::from(home).join(".second-brain").join("graph.kuzu")
        });

    if let Some(parent) = db_path.parent() {
        std::fs::create_dir_all(parent).ok();
    }

    let config_dir = MachineIdentity::config_dir()
        .expect("cannot determine home directory");
    let machine = MachineIdentity::load_or_create(&config_dir)
        .expect("failed to load or create machine identity");

    tracing::info!("machine identity: {} ({})", machine.name, machine.id);

    let store = match KuzuStore::open(&db_path, machine.id.clone()) {
        Ok(s) => Arc::new(s),
        Err(e) => {
            tracing::error!("failed to open database at {}: {e}", db_path.display());
            std::process::exit(1);
        }
    };
    store.register_machine(&machine)
        .expect("failed to register machine identity");

    tracing::info!("database opened at {}", db_path.display());

    let embedder = match Embedder::new() {
        Ok(e) => {
            tracing::info!("embedding model loaded");
            Some(Arc::new(e))
        }
        Err(e) => {
            tracing::warn!("embedding model unavailable: {e}");
            None
        }
    };

    watcher::spawn(store.clone(), embedder.clone(), machine.id.clone());

    if let Some(emb) = embedder.clone() {
        tokio::spawn(async move {
            let mut interval = tokio::time::interval(Duration::from_secs(60));
            loop {
                interval.tick().await;
                emb.drop_if_idle(300);
            }
        });
    }

    let state = Arc::new(AppState { store, embedder, machine });

    let app = Router::new()
        .merge(routes::router())
        .layer(DefaultBodyLimit::max(64 * 1024 * 1024))
        .layer(CorsLayer::permissive())
        .layer(TraceLayer::new_for_http())
        .with_state(state);

    let bind = std::env::var("SECOND_BRAIN_BIND").unwrap_or_else(|_| "127.0.0.1:7200".to_string());
    let listener = TcpListener::bind(&bind).await.unwrap();
    tracing::info!("listening on {bind}");

    axum::serve(listener, app).await.unwrap();
}
