use eyre::Context;
use newton_data_provider::{DataProviderConfig, WasmExecutor, WasmExecutorConfig};
use newton_prover_core::config::{NewtonAvsConfig, NewtonAvsConfigBuilder};
use std::path::PathBuf;

use crate::config::NewtonCliConfig;

/// Execute WASM with the given file path and input JSON
pub async fn execute_wasm(
    wasm_file: PathBuf,
    input_json: String,
    config: NewtonAvsConfig<NewtonCliConfig>,
) -> eyre::Result<String> {
    // Read WASM file
    let wasm_bytes = std::fs::read(&wasm_file).with_context(|| format!("Failed to read WASM file: {:?}", wasm_file))?;

    // Load DataProviderConfig from data-provider.toml
    let data_provider_config = NewtonAvsConfigBuilder::new(config.chain_id)
        .build::<DataProviderConfig>()
        .map_err(|e| eyre::eyre!("Failed to load data-provider config: {e}"))?;

    // Create executor config from the loaded DataProviderConfig
    let executor_config = WasmExecutorConfig::from_data_provider_config(&data_provider_config);
    let executor =
        WasmExecutor::new(executor_config).map_err(|e| eyre::eyre!("Failed to create WASM executor: {e}"))?;

    // Execute WASM with the input
    let result = executor
        .execute_wasm_bytes(&wasm_bytes, &input_json)
        .await
        .map_err(|e| eyre::eyre!("WASM execution failed: {e}"))?;

    match result {
        Ok(output) => Ok(output),
        Err(error) => {
            eyre::bail!("WASM execution error: {}", error);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::{fs, io::Write};
    use tempfile::TempDir;

    /// Helper to create a test config using the builder
    /// This will attempt to load from actual config files, which may fail in test environment
    fn create_test_config(chain_id: u64) -> eyre::Result<NewtonAvsConfig<NewtonCliConfig>> {
        NewtonAvsConfigBuilder::new(chain_id)
            .build::<NewtonCliConfig>()
            .map_err(|e| eyre::eyre!("Failed to build test config: {}", e))
    }

    /// Helper to create a temporary WASM file with invalid content (for error testing)
    fn create_invalid_wasm_file(dir: &TempDir) -> PathBuf {
        let wasm_path = dir.path().join("test.wasm");
        let mut file = fs::File::create(&wasm_path).unwrap();
        // Write invalid WASM bytes (not a valid WASM module)
        file.write_all(b"invalid wasm content").unwrap();
        wasm_path
    }

    /// Helper to create a non-existent file path
    fn create_nonexistent_path() -> PathBuf {
        PathBuf::from("/nonexistent/path/to/file.wasm")
    }

    #[tokio::test]
    async fn test_execute_wasm_file_not_found() {
        let config = match create_test_config(11155111) {
            Ok(c) => c,
            Err(_) => {
                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
                return;
            }
        };
        let nonexistent_file = create_nonexistent_path();
        let input_json = r#"{}"#.to_string();

        let result = execute_wasm(nonexistent_file, input_json, config).await;

        assert!(result.is_err(), "Should fail when WASM file doesn't exist");
        let error_msg = result.unwrap_err().to_string();
        assert!(
            error_msg.contains("Failed to read WASM file") || error_msg.contains("No such file"),
            "Error message should mention file reading failure. Got: {}",
            error_msg
        );
    }

    #[tokio::test]
    async fn test_execute_wasm_invalid_wasm_content() {
        let config = match create_test_config(11155111) {
            Ok(c) => c,
            Err(_) => {
                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
                return;
            }
        };
        let temp_dir = TempDir::new().unwrap();
        let wasm_file = create_invalid_wasm_file(&temp_dir);
        let input_json = r#"{}"#.to_string();

        let result = execute_wasm(wasm_file, input_json, config).await;

        // This should fail either at executor creation or WASM execution
        assert!(result.is_err(), "Should fail with invalid WASM content");
        let error_msg = result.unwrap_err().to_string();
        // The error could be about WASM compilation or execution
        assert!(
            error_msg.contains("WASM")
                || error_msg.contains("wasm")
                || error_msg.contains("executor")
                || error_msg.contains("compile"),
            "Error message should mention WASM-related failure. Got: {}",
            error_msg
        );
    }

    #[tokio::test]
    async fn test_execute_wasm_empty_input_json() {
        let config = match create_test_config(11155111) {
            Ok(c) => c,
            Err(_) => {
                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
                return;
            }
        };
        let temp_dir = TempDir::new().unwrap();
        let wasm_file = create_invalid_wasm_file(&temp_dir);
        let input_json = String::new();

        let result = execute_wasm(wasm_file, input_json, config).await;

        // Should still fail due to invalid WASM, but input_json being empty shouldn't cause a different error
        assert!(result.is_err(), "Should fail with invalid WASM even with empty input");
    }

    #[tokio::test]
    async fn test_execute_wasm_valid_json_input() {
        let config = match create_test_config(11155111) {
            Ok(c) => c,
            Err(_) => {
                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
                return;
            }
        };
        let temp_dir = TempDir::new().unwrap();
        let wasm_file = create_invalid_wasm_file(&temp_dir);
        let input_json = r#"{"key": "value", "number": 42}"#.to_string();

        let result = execute_wasm(wasm_file, input_json, config).await;

        // Should fail due to invalid WASM, but JSON parsing should work
        assert!(
            result.is_err(),
            "Should fail with invalid WASM even with valid JSON input"
        );
    }

    #[tokio::test]
    async fn test_execute_wasm_different_chain_ids() {
        let temp_dir = TempDir::new().unwrap();
        let wasm_file = create_invalid_wasm_file(&temp_dir);
        let input_json = r#"{}"#.to_string();

        // Test with different chain IDs
        // Note: Some chain IDs may not have complete config files, which is expected
        for chain_id in [1, 11155111, 17000] {
            // Use catch_unwind to handle panics from config loading (some chain IDs may not have all required files)
            let config_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| create_test_config(chain_id)));

            let config = match config_result {
                Ok(Ok(c)) => c,
                Ok(Err(e)) => {
                    eprintln!("Skipping chain_id {}: config loading failed: {}", chain_id, e);
                    continue;
                }
                Err(_) => {
                    eprintln!(
                        "Skipping chain_id {}: config loading panicked (missing deployment files)",
                        chain_id
                    );
                    continue;
                }
            };

            let result = execute_wasm(wasm_file.clone(), input_json.clone(), config).await;

            // Should fail due to invalid WASM, but config loading should work for supported chain IDs
            assert!(
                result.is_err(),
                "Should fail with invalid WASM for chain_id {}",
                chain_id
            );
        }
    }

    #[tokio::test]
    async fn test_execute_wasm_error_context_preserved() {
        let config = match create_test_config(11155111) {
            Ok(c) => c,
            Err(_) => {
                eprintln!("Skipping test: config loading failed (this is expected in some test environments)");
                return;
            }
        };
        let nonexistent_file = create_nonexistent_path();
        let input_json = r#"{}"#.to_string();

        let result = execute_wasm(nonexistent_file.clone(), input_json, config).await;

        assert!(result.is_err(), "Should fail when file doesn't exist");
        let error_msg = result.unwrap_err().to_string();
        // Verify the error context includes the file path
        let file_str = nonexistent_file.to_string_lossy();
        assert!(
            error_msg.contains("Failed to read WASM file") || error_msg.contains(&*file_str),
            "Error should include context about the file. Got: {}",
            error_msg
        );
    }

    // Note: Testing successful execution would require a valid WASM component file,
    // which is complex to create. Integration tests would be better suited for that.
    // These unit tests focus on error paths and input validation.
}
