mod auth;
mod config;
mod db;
mod entity;
mod handlers;
mod migration;
mod models;
mod voice;

use std::{net::SocketAddr, sync::Arc};

use anyhow::Context;
use axum::{Router, routing::get};
use sea_orm::Database;
use sea_orm_migration::MigratorTrait;
use tower_http::{services::ServeDir, trace::TraceLayer};
use tracing::info;

use crate::{config::Settings, handlers::routes, migration::Migrator, voice::VoiceHub};

#[derive(Clone)]
pub struct AppState {
    pub db: sea_orm::DatabaseConnection,
    pub settings: Arc<Settings>,
    pub http: reqwest::Client,
    pub voice: Arc<VoiceHub>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    dotenvy::dotenv().ok();
    init_tracing();

    let settings = Arc::new(Settings::from_env()?);

    let db = Database::connect(&settings.database_url)
        .await
        .with_context(|| "failed to connect to postgres")?;

    Migrator::up(&db, None)
        .await
        .with_context(|| "failed to run migrations")?;

    let state = AppState {
        db,
        settings,
        http: reqwest::Client::new(),
        voice: Arc::new(VoiceHub::default()),
    };
    let port = state.settings.port;

    let app = Router::new()
        .route("/health", get(handlers::health))
        .nest_service("/static", ServeDir::new("static"))
        .merge(routes())
        .with_state(state)
        .layer(TraceLayer::new_for_http());

    let addr = SocketAddr::from(([0, 0, 0, 0], port));
    info!(%addr, "server started");

    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, app).await?;
    Ok(())
}

fn init_tracing() {
    let filter = tracing_subscriber::EnvFilter::try_from_default_env()
        .unwrap_or_else(|_| "chattz=info,tower_http=info".into());

    tracing_subscriber::fmt().with_env_filter(filter).init();
}
