#![cfg_attr(not(test), deny(clippy::unwrap_used))]

pub mod config;
pub mod error;
#[cfg(feature = "http")]
pub mod handlers;
#[cfg(feature = "http")]
pub mod middleware;
pub mod models;
pub mod repositories;
#[cfg(feature = "http")]
mod router;
pub mod services;
pub mod storage;
pub mod workers;

pub use config::Config;
pub use error::{Result, SunscreenError};
pub use models::{
    AddressCheckResult, Currency, SanctionedAddress, SanctionedCountry, SanctionsListResponse,
};
pub use services::{ApiKeyCache, IngestionService};
pub use storage::Storage;

use std::sync::Arc;

/// Shared application state for the HTTP server.
pub struct AppState {
    pub storage: Storage,
    pub api_key_cache: ApiKeyCache,
}

/// Create a composable axum `Router` from a pre-built `Storage` and `ApiKeyCache`.
///
/// This is the primary integration point for embedding cedros-sunscreen
/// into a larger application (like cedros-rust-backend).
#[cfg(feature = "http")]
pub fn router(storage: Storage, api_key_cache: ApiKeyCache) -> axum::Router {
    router_with_cors(storage, api_key_cache, &[])
}

/// Create a composable axum `Router` with CORS configuration.
#[cfg(feature = "http")]
pub fn router_with_cors(
    storage: Storage,
    api_key_cache: ApiKeyCache,
    cors_origins: &[String],
) -> axum::Router {
    let state = Arc::new(AppState {
        storage,
        api_key_cache,
    });
    router::build_router(state, cors_origins)
}

/// Run the HTTP server as a standalone service.
///
/// Runs migrations, loads the API key cache, starts background workers, then
/// binds to the configured address.
#[cfg(feature = "http")]
pub async fn run_http_server(
    config: Config,
    http_config: config::HttpServerConfig,
) -> Result<()> {
    use std::time::Duration;
    use tokio::net::TcpListener;
    use tracing::info;

    let storage = Storage::from_database_url(&config.database_url).await?;

    // Run migrations on startup.
    storage.migrate().await?;
    info!("database migrations applied");

    let api_key_cache = ApiKeyCache::new();

    // Initial cache load before accepting requests.
    api_key_cache.refresh(&storage.api_key_repo).await;

    // Spawn background workers.
    let _ingestion_handle = workers::create_ingestion_worker(
        IngestionService::new(
            config.ofac_xml_url.clone(),
            config.ransomware_url.clone(),
        ),
        storage.address_repo.clone(),
        storage.country_repo.clone(),
        Duration::from_secs(config.data_refresh_interval_secs),
    );

    let _key_refresh_handle = workers::create_api_key_refresh_worker(
        api_key_cache.clone(),
        storage.api_key_repo.clone(),
        Duration::from_secs(config.api_key_refresh_interval_secs),
    );

    let app = router_with_cors(storage, api_key_cache, &http_config.cors_allowed_origins);
    let listener = TcpListener::bind(&http_config.bind_addr).await.map_err(|e| {
        SunscreenError::InvalidRequest(format!("failed to bind to {}: {e}", http_config.bind_addr))
    })?;

    info!(bind_addr = %http_config.bind_addr, "cedros-sunscreen HTTP server listening");
    axum::serve(listener, app)
        .await
        .map_err(|e| SunscreenError::InvalidRequest(format!("server error: {e}")))
}
