use crate::graph::node::RunnableNode;
use async_trait::async_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use wasmtime::component::{Component, Linker};
use wasmtime::{Config, Engine, Store};

mod dynamic_state;
use crate::graph::dynamic::csv_readers::CsvReadersData;
use crate::graph::dynamic::csv_writer::CsvWriterData;
use crate::graph::dynamic::read_map::ReadMapData;
use dynamic_state::{Dynamic, DynamicState};

mod csv_reader;
mod csv_readers;
mod csv_row;
mod csv_writer;
mod read_map;

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct DynamicNode {
    pub wasm_file_path: String,
    pub input_file_paths: HashMap<String, String>,
    pub output_file: String,
    pub properties: HashMap<String, String>,
}

// Node to run arbitrary webassembly code to transform one or more csv files
// TODO: Create a separate node for wit that allows arbitrary files
pub struct DynamicNodeRunner {
    pub dynamic_node: DynamicNode,
}

#[async_trait]
impl RunnableNode for DynamicNodeRunner {
    async fn run(&self) -> anyhow::Result<()> {
        let engine = Engine::new(Config::new().wasm_component_model(true))?;
        let component = Component::from_file(&engine, &self.dynamic_node.wasm_file_path)?;
        let mut linker = Linker::new(&engine);
        Dynamic::add_to_linker(&mut linker, |state: &mut DynamicState| state)?;
        let mut store = Store::new(&engine, DynamicState::new());
        let bindings = Dynamic::instantiate(&mut store, &component, &linker)?;
        let read_map = store.data_mut().resources.push(ReadMapData {
            data: self.dynamic_node.properties.clone(),
        })?;
        let readers = store.data_mut().resources.push(CsvReadersData {
            readers: self.dynamic_node.input_file_paths.clone(),
        })?;
        let writer = CsvWriterData::new(self.dynamic_node.output_file.clone())?;
        let writer = store.data_mut().resources.push(writer)?;
        bindings.call_evaluate(&mut store, read_map, readers, writer)?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::graph::dynamic::{DynamicNode, DynamicNodeRunner};
    use crate::graph::node::RunnableNode;
    use std::collections::HashMap;

    #[tokio::test]
    async fn move_money_wasm() -> anyhow::Result<()> {
        let mut input_file_paths = HashMap::new();
        input_file_paths.insert("Account".to_owned(), "testing/input/account.csv".to_owned());
        input_file_paths.insert(
            "CostCentre".to_owned(),
            "testing/input/costcentre.csv".to_owned(),
        );
        input_file_paths.insert(
            "Line".to_owned(),
            "testing/input/move_money/line.csv".to_owned(),
        );
        input_file_paths.insert(
            "Rule".to_owned(),
            "testing/input/move_money/reclassrule.csv".to_owned(),
        );
        let mut properties = HashMap::new();
        properties.insert("flush_pass".to_owned(), "false".to_owned());
        // properties.insert("flush_pass".to_owned(), "true".to_owned());
        properties.insert("use_numeric_accounts".to_owned(), "false".to_owned());
        let dynamic_node = DynamicNode {
            input_file_paths,
            output_file: "testing/output/output.csv".to_owned(),
            wasm_file_path: "target/wasm32-unknown-unknown/release/move_money_dynamic.wasm"
                .to_owned(),
            properties,
        };
        let runner = DynamicNodeRunner { dynamic_node };
        runner.run().await?;
        Ok(())
    }
}
