use anyhow::Result;
use wasmtime::component::types::ComponentItem;
use wasmtime::component::{Component, Linker, Type};
use wasmtime::{Engine, Module, Precompiled, Store};

#[test]
fn module_component_mismatch() -> Result<()> {
    let engine = super::engine();
    let module = Module::new(&engine, "(module)")?.serialize()?;
    let component = Component::new(&engine, "(component)")?.serialize()?;

    unsafe {
        assert!(Module::deserialize(&engine, &component).is_err());
        assert!(Component::deserialize(&engine, &module).is_err());
    }

    Ok(())
}

#[test]
fn bare_bones() -> Result<()> {
    let engine = super::engine();
    let component = Component::new(&engine, "(component)")?.serialize()?;
    assert_eq!(component, engine.precompile_component(b"(component)")?);

    let component = unsafe { Component::deserialize(&engine, &component)? };
    let mut store = Store::new(&engine, ());
    Linker::new(&engine).instantiate(&mut store, &component)?;

    Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn mildly_more_interesting() -> Result<()> {
    let engine = super::engine();
    let component = Component::new(
        &engine,
        r#"
            (component
                (core module $a
                    (func (export "a") (result i32)
                        i32.const 100)
                )
                (core instance $a (instantiate $a))

                (core module $b
                    (import "a" "a" (func $import (result i32)))
                    (func (export "a") (result i32)
                        call $import
                        i32.const 3
                        i32.add)
                )
                (core instance $b (instantiate $b (with "a" (instance $a))))

                (func (export "a") (result u32)
                    (canon lift (core func $b "a"))
                )
            )
        "#,
    )?
    .serialize()?;

    let component = unsafe { Component::deserialize(&engine, &component)? };
    let mut store = Store::new(&engine, ());
    let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
    let func = instance.get_typed_func::<(), (u32,)>(&mut store, "a")?;
    assert_eq!(func.call(&mut store, ())?, (103,));

    Ok(())
}

#[test]
fn deserialize_from_serialized() -> Result<()> {
    let engine = super::engine();
    let buffer1 = Component::new(&engine, "(component (core module))")?.serialize()?;
    let buffer2 = unsafe { Component::deserialize(&engine, &buffer1)?.serialize()? };
    assert!(buffer1 == buffer2);
    Ok(())
}

// This specifically tests the current behavior that it's an error, but this can
// be made to work if necessary in the future. Currently the implementation of
// `serialize` is not conducive to easily implementing this feature and
// otherwise it's not seen as too important to implement.
#[test]
fn cannot_serialize_exported_module() -> Result<()> {
    let engine = super::engine();
    let component = Component::new(
        &engine,
        r#"(component
            (core module $m)
            (export "a" (core module $m))
        )"#,
    )?;
    let mut store = Store::new(&engine, ());
    let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
    let module = instance.get_module(&mut store, "a").unwrap();
    assert!(module.serialize().is_err());
    Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn usable_exported_modules() -> Result<()> {
    let engine = super::engine();
    let component = Component::new(
        &engine,
        r#"(component
            (core module $m)
            (core module $m1 (export "a")
                (import "" "" (func (param i32)))
            )
        )"#,
    )?;
    let mut store = Store::new(&engine, ());
    let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
    let module = instance.get_module(&mut store, "a").unwrap();
    let mut core_linker = wasmtime::Linker::new(&engine);
    core_linker.func_wrap("", "", |_: u32| {})?;
    core_linker.instantiate(&mut store, &module)?;
    Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn detect_precompiled() -> Result<()> {
    let engine = super::engine();
    let buffer = Component::new(&engine, "(component)")?.serialize()?;
    assert_eq!(Engine::detect_precompiled(&[]), None);
    assert_eq!(Engine::detect_precompiled(&buffer[..5]), None);
    assert_eq!(
        Engine::detect_precompiled(&buffer),
        Some(Precompiled::Component)
    );
    Ok(())
}

#[test]
fn reflect_resource_import() -> Result<()> {
    let engine = super::engine();
    let c = Component::new(
        &engine,
        r#"
        (component
            (import "x" (type $x (sub resource)))
            (import "y" (func (result (own $x))))
        )
        "#,
    )?;
    let ty = c.component_type();
    let mut imports = ty.imports(&engine);
    let (_, x) = imports.next().unwrap();
    let (_, y) = imports.next().unwrap();
    let x = match x {
        ComponentItem::Resource(t) => t,
        _ => unreachable!(),
    };
    let y = match y {
        ComponentItem::ComponentFunc(t) => t,
        _ => unreachable!(),
    };
    let result = y.results().next().unwrap();
    assert_eq!(result, Type::Own(x));

    Ok(())
}

#[test]
#[cfg_attr(miri, ignore)]
fn truncated_component_binaries_dont_panic() -> Result<()> {
    let engine = super::engine();

    let binary = wat::parse_str(
        r#"
        (component
            (import "a" (core module $m0
                (import "" "" (func))
            ))

            (core module $m1
                (func (export ""))
            )
            (core instance $i1 (instantiate (module $m1)))
            (func $f (canon lift (core func $i1 "f")))

            (component $c1
                (import "f" (func))
                (core module $m2
                    (func (export "g"))
                )
                (core instance $i2 (instantiate $m2))
                (func (export "g")
                    (canon lift (core func $i2 "g"))
                )
            )
            (instance $i3 (instantiate $c1 (with "f" (func $f))))
            (func (export "g") (alias export $i3 "g"))
        )
        "#,
    )?;

    // Check that if we feed each truncation of the component binary into
    // `Component::new` we don't get any panics.
    for i in 1..binary.len() - 1 {
        let _ = Component::from_binary(&engine, &binary[0..i]);
    }

    Ok(())
}
