use std::sync::Arc;

use axum::{extract::FromRef, http::StatusCode, routing::get, Router};
use tokio::io;

use crate::{
    config::AppConfig,
    constants::routes::*,
    controllers,
    sources::{self, file::FileSource, http::HttpSource, manager::SourceManagerHandle, SourceList},
};

#[derive(Debug, Clone, FromRef)]
struct AppState {
    config: Arc<AppConfig>,
    source_handles: Vec<SourceManagerHandle>,
}

pub async fn create_app(config: Arc<AppConfig>) -> Result<Router, Error> {
    let mut app_state = AppState {
        config: config.clone(),
        source_handles: vec![],
    };

    if let Some(path) = config.local_list_path() {
        let handle =
            sources::manager::spawn(FileSource::new(path.to_owned(), config.source_lifetime()))
                .await?;
        app_state.source_handles.push(handle);
    }

    if let Some(path) = config.sources_path() {
        let file = tokio::fs::read(path).await?;
        let sources: SourceList = serde_json::from_slice(&file)?;

        for url in sources.sources() {
            let handle =
                sources::manager::spawn(HttpSource::new(url.to_owned(), config.source_lifetime()))
                    .await?;
            app_state.source_handles.push(handle);
        }
    }

    Ok(Router::new()
        .route(HEALTH_CHECK_ROUTE, get(controllers::health_check))
        .route(FETCH_ALL_ROUTE, get(controllers::list::fetch_all))
        .route(
            FETCH_ALL_CSV_ROUTE,
            get(controllers::list::fetch_all_as_mastodon_csv),
        )
        .route(SEARCH_LIST_ROUTE, get(controllers::list::search_domain))
        .fallback(|| async { StatusCode::NOT_FOUND })
        .with_state(app_state))
}

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("failed to read from local source")]
    File(#[from] io::Error),

    #[error("failed to deserialize local source")]
    Deserialize(#[from] serde_json::Error),

    #[error("failed to spawn actor")]
    Manager(#[from] sources::manager::Error),
}
