pub mod api;
pub mod binary_format;
pub mod cli;
pub mod logging;
pub mod runtime;
pub mod seq_util;
pub mod types;

mod app_version_sdk;
mod config;
mod error;
mod experiment;
mod job;
mod multipart_upload;
mod resource_uri;

pub use app_version_sdk::AppVersionSdk;
pub use config::Config;
pub use error::{BioLibError, Result};
pub use experiment::Experiment;
pub use job::{Job, JobResult};
pub use resource_uri::{parse_resource_uri, ResourceUri};
pub use runtime::Runtime;
pub use seq_util::{ParseFastaOptions, SeqUtil, SeqUtilRecord};

use api::ApiClient;
use std::sync::Once;

static INIT_LOGGING: Once = Once::new();

fn ensure_logging() {
    INIT_LOGGING.call_once(|| {
        logging::configure(20);
    });
}

pub struct BioLibApp {
    app_uri: String,
    app: types::app::App,
    app_version: types::app::AppVersion,
    api_client: ApiClient,
}

impl BioLibApp {
    pub fn load(uri: &str) -> Result<Self> {
        let config = Config::load();
        let api_client = ApiClient::new(&config)?;
        let response = api_client.get_app_by_uri(uri)?;

        Ok(Self {
            app_uri: response.app_uri,
            app: response.app,
            app_version: response.app_version,
            api_client,
        })
    }

    pub fn load_with_client(uri: &str, api_client: ApiClient) -> Result<Self> {
        let response = api_client.get_app_by_uri(uri)?;

        Ok(Self {
            app_uri: response.app_uri,
            app: response.app,
            app_version: response.app_version,
            api_client,
        })
    }

    pub fn uri(&self) -> &str {
        &self.app_uri
    }

    pub fn uuid(&self) -> &str {
        &self.app.public_id
    }

    pub fn version(&self) -> &types::app::AppVersion {
        &self.app_version
    }

    pub fn sdk(&self) -> Result<AppVersionSdk> {
        let config = Config::load();
        let api_client = ApiClient::new(&config)?;
        Ok(AppVersionSdk::new(self.app_version.clone(), api_client))
    }

    pub fn cli(
        &self,
        args: Option<Vec<String>>,
        stdin: Option<Vec<u8>>,
        files: Option<std::collections::HashMap<String, Vec<u8>>>,
        blocking: bool,
    ) -> Result<Job> {
        let args = args.unwrap_or_default();
        let stdin = stdin.unwrap_or_default();
        let files = files.unwrap_or_default();

        let module_input = binary_format::ModuleInput::serialize(&stdin, &args, &files)?;

        let mut job = Job::start_in_cloud(
            &self.api_client,
            &self.app_uri,
            &self.app_version.public_id,
            &module_input,
            false,
            None,
            None,
            None,
            None,
            false,
            None,
        )?;

        if blocking {
            job.stream_logs()?;
        }

        Ok(job)
    }
}

pub fn load(uri: &str) -> Result<BioLibApp> {
    ensure_logging();
    BioLibApp::load(uri)
}

pub fn get_result(result_id: &str, result_token: Option<&str>) -> Result<Job> {
    ensure_logging();
    let config = Config::load();
    let api_client = ApiClient::new(&config)?;
    Job::from_uuid(&api_client, result_id, result_token)
}

pub fn get_job(job_id: &str, job_token: Option<&str>) -> Result<Job> {
    get_result(job_id, job_token)
}

pub fn search(query: Option<&str>, team: Option<&str>, count: usize) -> Result<Vec<String>> {
    ensure_logging();
    let config = Config::load();
    let api_client = ApiClient::new(&config)?;
    api_client.search_apps(query, team, count)
}

pub fn fetch_jobs(count: usize, status: Option<&str>) -> Result<Vec<Job>> {
    ensure_logging();
    let config = Config::load();
    let api_client = ApiClient::new(&config)?;
    Job::fetch_jobs(&api_client, count, status)
}
