use concepts::{ComponentType, FunctionFqn, FunctionMetadata, StrVariant};
use std::{error::Error, fmt::Debug, path::Path};
use tracing::{debug, trace};
use tracing_error::SpanTrace;
use utils::wasm_tools::{self, DecodeError, ExIm, WasmComponent};

pub mod activity;
pub mod cancellation_driver;
pub mod component_logger;
pub mod cron;
pub mod engines;
pub mod epoch_ticker;
pub mod http_hooks;
pub mod http_request_policy;
pub(crate) mod js_imports;
pub(crate) mod js_worker_utils;
pub mod log_db_forwarder;
pub(crate) mod policy_builder;
pub mod registry;
pub mod std_output_stream;
#[cfg(any(test, feature = "test"))]
pub mod testing_fn_registry;
pub mod webhook;
pub mod workflow;

#[derive(thiserror::Error, Debug)]
pub enum WasmFileError {
    #[error("cannot decode: {0}")]
    DecodeError(
        #[from]
        #[source]
        wasm_tools::DecodeError,
    ),
    #[error("linking error - {reason}, details: {err}")]
    LinkingError {
        reason: StrVariant,
        #[source]
        err: Box<dyn Error + Send + Sync>,
        context: SpanTrace,
    },
}
impl WasmFileError {
    pub fn linking_error(
        reason: impl Into<StrVariant>,
        error: impl Into<Box<dyn Error + Send + Sync>>,
    ) -> WasmFileError {
        WasmFileError::LinkingError {
            reason: reason.into(),
            err: error.into(),
            context: SpanTrace::capture(),
        }
    }
}

pub mod envvar {
    #[derive(Clone, derive_more::Debug)]
    pub struct EnvVar {
        pub key: String,
        #[debug(skip)]
        pub val: String,
    }
}

#[derive(derive_more::Debug, Clone)]
pub struct RunnableComponent {
    #[debug(skip)]
    pub wasmtime_component: wasmtime::component::Component,
    pub wasm_component: WasmComponent,
}
impl RunnableComponent {
    pub fn new<P: AsRef<Path>>(
        wasm_path: P,
        engine: &wasmtime::Engine,
        component_type: ComponentType,
    ) -> Result<Self, DecodeError> {
        let wasm_path = wasm_path.as_ref();
        let wasm_component = WasmComponent::new(wasm_path, component_type)?;
        trace!("Decoding using wasmtime");
        let wasmtime_component = {
            let stopwatch = std::time::Instant::now();
            let wasmtime_component = wasmtime::component::Component::from_file(engine, wasm_path)
                .map_err(|err| {
                DecodeError::new_with_source(
                    format!("cannot parse {wasm_path:?} using wasmtime"),
                    err,
                )
            })?;
            debug!("Parsed with wasmtime in {:?}", stopwatch.elapsed());
            wasmtime_component
        };
        Ok(Self {
            wasmtime_component,
            wasm_component,
        })
    }

    pub fn index_exported_functions(
        wasmtime_component: &wasmtime::component::Component,
        exim: &ExIm,
    ) -> Result<
        hashbrown::HashMap<FunctionFqn, wasmtime::component::ComponentExportIndex>,
        DecodeError,
    > {
        let mut exported_ffqn_to_index = hashbrown::HashMap::new();
        for FunctionMetadata { ffqn, .. } in exim.get_exports(false) {
            let Some(ifc_export_index) = wasmtime_component.get_export_index(None, &*ffqn.ifc_fqn)
            else {
                return Err(DecodeError::new_without_source(format!(
                    "cannot find exported interface {ffqn}"
                )));
            };
            let Some(fn_export_index) =
                wasmtime_component.get_export_index(Some(&ifc_export_index), &*ffqn.function_name)
            else {
                return Err(DecodeError::new_without_source(format!(
                    "cannot find exported function {ffqn}"
                )));
            };
            exported_ffqn_to_index.insert(ffqn.clone(), fn_export_index);
        }
        Ok(exported_ffqn_to_index)
    }
}

#[cfg(test)]
pub(crate) mod tests {

    mod populate_codegen_cache {
        use crate::{
            activity::activity_worker::test::compile_activity,
            workflow::workflow_worker::test::compile_workflow,
        };

        #[rstest::rstest(wasm_path => [
            test_programs_fibo_activity_builder::TEST_PROGRAMS_FIBO_ACTIVITY,
            test_programs_http_get_activity_builder::TEST_PROGRAMS_HTTP_GET_ACTIVITY,
            test_programs_sleep_activity_builder::TEST_PROGRAMS_SLEEP_ACTIVITY,
            activity_js_runtime_builder::ACTIVITY_JS_RUNTIME,
            ])]
        #[tokio::test]
        async fn activity(wasm_path: &str) {
            compile_activity(wasm_path).await;
        }

        #[rstest::rstest(wasm_path => [
            test_programs_fibo_workflow_builder::TEST_PROGRAMS_FIBO_WORKFLOW,
            test_programs_http_get_workflow_builder::TEST_PROGRAMS_HTTP_GET_WORKFLOW,
            test_programs_sleep_workflow_builder::TEST_PROGRAMS_SLEEP_WORKFLOW,
            workflow_js_runtime_builder::WORKFLOW_JS_RUNTIME,
            ])]
        #[tokio::test]
        async fn workflow(wasm_path: &str) {
            compile_workflow(wasm_path).await;
        }

        #[rstest::rstest(wasm_path => [
            test_programs_fibo_webhook_builder::TEST_PROGRAMS_FIBO_WEBHOOK,
            webhook_js_runtime_builder::WEBHOOK_JS_RUNTIME,
            ])]
        #[test]
        fn webhook(wasm_path: &str) {
            crate::webhook::webhook_trigger::tests::compile_webhook(wasm_path);
        }
    }
}
