use crate::parse::config::Injection;
use anyhow::Context;
use cviz::model::{compatible_fingerprints, ExportInfo};
use cviz::parse::component::{parse_component, parse_component_imports};
use std::collections::{BTreeMap, HashMap};
use std::fs;

// Generated by build.rs from wit/tier1/world.wit — single source of truth.
include!(concat!(env!("OUT_DIR"), "/tier_interfaces.rs"));

/// Append `@version` to an unversioned interface name, producing the
/// form used in component import/export names (e.g.
/// `"splicer:tier1/before"` + `"0.2.0"` → `"splicer:tier1/before@0.2.0"`).
pub fn versioned_interface(iface: &str, version: &str) -> String {
    format!("{iface}@{version}")
}

/// The outcome of a single middleware contract check.
#[derive(Clone, Debug, PartialEq)]
pub enum ContractResult {
    /// The middleware exports the interface and the type fingerprints match.
    Ok,
    /// The middleware could not be validated — either no path was provided or
    /// the middleware does not export the contracted interface.  Injection can
    /// still proceed, but type safety is unconfirmed.
    Warn(String),
    /// The middleware exports the interface but with an incompatible type
    /// fingerprint.  Injection should be blocked.
    Error(String),
    /// The middleware does not export the target interface but does export at
    /// least one tier-1 type-erased interface (`splicer:tier1/{before,after,blocking}`).
    /// The inner list names the matched interfaces so the adapter generator knows
    /// exactly which hooks to wire up.
    Tier1Compatible(Vec<String>),
    /// The middleware exports at least one tier-2 interface
    /// (`splicer:tier2/{before,after,trap}`). The inner list names the
    /// matched interfaces so the adapter generator knows exactly
    /// which hooks to wire up.
    Tier2Compatible(Vec<String>),
}

/// Check that every middleware in `to_inject` is type-compatible with the
/// interface being contracted on.
///
/// Returns one [`ContractResult`] per injection in the same order.
/// Callers are responsible for acting on the results (logging, aborting, etc.).
pub fn validate_contract(
    to_inject: &[Injection],
    interface_name: &str,
    contract_fingerprint: &Option<String>,
    checked_middlewares: &mut HashMap<String, BTreeMap<String, ExportInfo>>,
) -> Vec<ContractResult> {
    let mut results = vec![];
    for Injection { name, path, .. } in to_inject.iter() {
        if !checked_middlewares.contains_key(name.as_str()) {
            match discover_middleware_exports(path) {
                Ok(exports) => {
                    checked_middlewares.insert(name.clone(), exports);
                }
                Err(err) => {
                    results.push(ContractResult::Warn(format!(
                        "Unable to load middleware '{name}': {err:#}"
                    )));
                    continue;
                }
            }
        }
        let exports = checked_middlewares.get(name.as_str()).unwrap();

        if let Some(ExportInfo { fingerprint, .. }) = exports.get(interface_name) {
            if !compatible_fingerprints(contract_fingerprint, fingerprint) {
                results.push(ContractResult::Error(format!(
                    "incompatible type signatures for middleware '{}' on interface '{}'\n\t{name}:\t{fingerprint:?}\n\ttarget: {contract_fingerprint:?}",
                    name, interface_name
                )));
            } else {
                results.push(ContractResult::Ok);
            }
        } else {
            results.push(classify_tier_export(name, exports, path, interface_name));
        }
    }
    results
}

/// Classify a middleware that doesn't export the target interface
/// directly: detect which (if any) `splicer:tierN/*` package it
/// exports, enforce the one-tier-per-middleware rule, and return the
/// appropriate `ContractResult`.
fn classify_tier_export(
    name: &str,
    exports: &BTreeMap<String, ExportInfo>,
    path: &Option<String>,
    interface_name: &str,
) -> ContractResult {
    let tier1 = match_tier_interfaces(exports, TIER1_INTERFACES, TIER1_VERSION);
    let tier2 = match_tier_interfaces(exports, TIER2_INTERFACES, TIER2_VERSION);

    // One-tier-per-middleware: reject any component exporting
    // interfaces from multiple tier packages. The user-facing rule
    // lives in `docs/adapter-components.md` ("One tier per middleware").
    if !tier1.is_empty() && !tier2.is_empty() {
        return ContractResult::Error(format!(
            "middleware '{name}' exports interfaces from multiple tiers \
             (tier 1: {tier1_list}; tier 2: {tier2_list}).\n\n\
             A middleware must implement exactly one tier. To combine \
             behaviors, ship them as separate components and chain \
             them in `inject: [...]`.",
            tier1_list = tier1.join(", "),
            tier2_list = tier2.join(", "),
        ));
    }

    if !tier1.is_empty() {
        if !is_adapter_eligible(path, interface_name) {
            return warn_not_exported(name, interface_name);
        }
        return ContractResult::Tier1Compatible(tier1);
    }

    if !tier2.is_empty() {
        if !is_adapter_eligible(path, interface_name) {
            return warn_not_exported(name, interface_name);
        }
        return ContractResult::Tier2Compatible(tier2);
    }

    warn_not_exported(name, interface_name)
}

fn warn_not_exported(name: &str, interface_name: &str) -> ContractResult {
    ContractResult::Warn(format!(
        "Middleware '{}' does not export interface '{}'.\n\
         \tIt cannot be spliced on this interface.\n\
         \tCheck that the middleware both imports and re-exports '{}',\n\
         \tor that the interface name in your config exactly matches\n\
         \twhat the middleware binary exports.",
        name, interface_name, interface_name
    ))
}

/// Returns `true` when a middleware is a candidate for tier-1 adapter generation.
///
/// A middleware is tier-1 compatible when:
/// 1. It exports one of the [`TIER1_INTERFACES`] — the positive signal that it was written
///    as a type-erased middleware.  cviz has already validated that the exported
///    interface is structurally sound when it produced the fingerprint.
/// 2. A path to the binary is provided — the adapter cannot be generated without it.
/// 3. It does **not** import `target_interface` — confirming it is not a regular
///    pass-through middleware that simply failed the fingerprint check.
///
/// Check whether `export_name` (possibly versioned, e.g.
/// `"splicer:tier1/before@0.2.0"`) matches the unversioned interface
/// constant `iface` (e.g. `"splicer:tier1/before"`) with semver
/// compatibility against `expected_version`.
///
/// - Exact match (no version suffix) always passes.
/// - Versioned match passes when the export's version is semver-
///   compatible with `expected_version`: same major (or both 0.x with
///   same minor).
fn is_compatible_interface(export_name: &str, iface: &str, expected_version: &str) -> bool {
    if export_name == iface {
        return true;
    }
    // Check for "iface@version" pattern.
    let Some(rest) = export_name.strip_prefix(iface) else {
        return false;
    };
    let Some(version_str) = rest.strip_prefix('@') else {
        return false;
    };
    let Ok(export_ver) = semver::Version::parse(version_str) else {
        return false;
    };
    // Semver compatibility: ^expected_version.
    // For 0.x: same major AND same minor (0.1.x matches 0.1.0).
    // For 1.x+: same major (1.x.y matches 1.0.0).
    let Ok(req) = semver::VersionReq::parse(&format!("^{expected_version}")) else {
        return false;
    };
    req.matches(&export_ver)
}

/// Return the subset of `tier_ifaces` that `exports` includes, with
/// semver-compatible version matching against `tier_version`.
///
/// Export keys may be versioned (e.g. `"splicer:tier1/before@0.2.0"`)
/// while the constants are unversioned (`"splicer:tier1/before"`).
/// Any export with a version semver-compatible with the version
/// splicer was built against passes (see [`is_compatible_interface`]).
fn match_tier_interfaces(
    exports: &BTreeMap<String, ExportInfo>,
    tier_ifaces: &[&str],
    tier_version: &str,
) -> Vec<String> {
    tier_ifaces
        .iter()
        .filter(|iface| {
            exports
                .keys()
                .any(|export_name| is_compatible_interface(export_name, iface, tier_version))
        })
        .map(|iface| iface.to_string())
        .collect()
}

/// Adapter generation is only viable when:
/// 1. A path to the middleware binary is provided — the adapter
///    generator needs the bytes.
/// 2. The middleware does **not** import `target_interface` —
///    confirming it isn't a regular pass-through middleware that
///    simply failed the fingerprint check.
fn is_adapter_eligible(middleware_path: &Option<String>, target_interface: &str) -> bool {
    let Some(path) = middleware_path else {
        return false;
    };
    let Ok(buff) = fs::read(path) else {
        return false;
    };
    let Ok(imports) = parse_component_imports(&buff) else {
        return false;
    };
    !imports.iter().any(|(name, _)| name == target_interface)
}

fn discover_middleware_exports(
    wasm_path: &Option<String>,
) -> anyhow::Result<BTreeMap<String, ExportInfo>> {
    let Some(path) = wasm_path else {
        return Ok(BTreeMap::default());
    };
    let buff = fs::read(path).with_context(|| format!("failed to read '{path}'"))?;
    let graph = parse_component(&buff)
        .with_context(|| format!("failed to parse Wasm component '{path}'"))?;
    Ok(graph.component_exports)
}

#[cfg(test)]
mod tests {
    use super::*;
    use cviz::model::ExportInfo;

    // ── is_compatible_interface tests ─────────────────────────────────

    #[test]
    fn exact_match_no_version() {
        assert!(is_compatible_interface(
            "splicer:tier1/before",
            "splicer:tier1/before",
            "0.1.0"
        ));
    }

    #[test]
    fn versioned_match_same_version() {
        assert!(is_compatible_interface(
            "splicer:tier1/before@0.1.0",
            "splicer:tier1/before",
            "0.1.0"
        ));
    }

    #[test]
    fn versioned_match_patch_bump() {
        assert!(is_compatible_interface(
            "splicer:tier1/before@0.1.3",
            "splicer:tier1/before",
            "0.1.0"
        ));
    }

    #[test]
    fn versioned_mismatch_minor_bump_0x() {
        // Under 0.x semver, minor bumps are breaking.
        assert!(!is_compatible_interface(
            "splicer:tier1/before@0.2.0",
            "splicer:tier1/before",
            "0.1.0"
        ));
    }

    #[test]
    fn versioned_mismatch_different_name() {
        assert!(!is_compatible_interface(
            "splicer:tier1/after@0.1.0",
            "splicer:tier1/before",
            "0.1.0"
        ));
    }

    #[test]
    fn versioned_match_major_1x_minor_bump() {
        // Under 1.x semver, minor bumps are compatible.
        assert!(is_compatible_interface(
            "splicer:tier1/before@1.2.0",
            "splicer:tier1/before",
            "1.0.0"
        ));
    }

    #[test]
    fn versioned_mismatch_major_bump() {
        assert!(!is_compatible_interface(
            "splicer:tier1/before@2.0.0",
            "splicer:tier1/before",
            "1.0.0"
        ));
    }

    // ── existing tests ───────────────────────────────────────────────

    fn discover_exports_from_bytes(bytes: &[u8]) -> BTreeMap<String, ExportInfo> {
        let graph = parse_component(bytes).expect("Unable to parse component");
        graph.component_exports
    }

    fn injection(name: &str) -> Injection {
        Injection {
            name: name.to_string(),
            adapter_info: None,
            builtin: None,
            path: None,
        }
    }

    fn export_with_fingerprint(fp: &str) -> ExportInfo {
        ExportInfo {
            source_instance: 0,
            fingerprint: Some(fp.to_string()),
            ty: None,
        }
    }

    /// Pre-populate the middleware cache so that discovery is bypassed.
    fn cache_with(
        mw_name: &str,
        interface: &str,
        fp: &str,
    ) -> HashMap<String, BTreeMap<String, ExportInfo>> {
        let mut exports = BTreeMap::new();
        exports.insert(interface.to_string(), export_with_fingerprint(fp));
        let mut cache = HashMap::new();
        cache.insert(mw_name.to_string(), exports);
        cache
    }

    // -----------------------------------------------------------------------
    // WARN: unable to validate
    // -----------------------------------------------------------------------

    #[test]
    fn warn_when_no_path() {
        // Injection with no path → discovery returns empty → cannot validate.
        let mut cache = HashMap::new();
        let results = validate_contract(&[injection("mw")], "wasi:http/handler", &None, &mut cache);
        assert_eq!(results.len(), 1);
        assert!(
            matches!(results[0], ContractResult::Warn(_)),
            "expected Warn, got {:?}",
            results[0]
        );
    }

    #[test]
    fn warn_when_interface_not_in_exports() {
        // Middleware is in cache but does not export the contracted interface.
        let mut exports = BTreeMap::new();
        exports.insert(
            "other:pkg/other".to_string(),
            export_with_fingerprint("fp-x"),
        );
        let mut cache = HashMap::new();
        cache.insert("mw".to_string(), exports);

        let results = validate_contract(
            &[injection("mw")],
            "wasi:http/handler",
            &Some("fp-a".to_string()),
            &mut cache,
        );
        assert_eq!(results.len(), 1);
        assert!(
            matches!(results[0], ContractResult::Warn(_)),
            "expected Warn, got {:?}",
            results[0]
        );
    }

    #[test]
    fn warn_for_each_injection_without_path() {
        // Multiple injections, all without paths — each should produce a Warn.
        let injections = vec![injection("mw-a"), injection("mw-b"), injection("mw-c")];
        let mut cache = HashMap::new();
        let results = validate_contract(&injections, "wasi:http/handler", &None, &mut cache);
        assert_eq!(results.len(), 3);
        assert!(results.iter().all(|r| matches!(r, ContractResult::Warn(_))));
    }

    // -----------------------------------------------------------------------
    // ERROR: incompatible fingerprints
    // -----------------------------------------------------------------------

    #[test]
    fn error_when_fingerprints_incompatible() {
        // Contract expects "fp-a"; middleware exports "fp-b" → Error.
        let mut cache = cache_with("mw", "wasi:http/handler", "fp-b");
        let results = validate_contract(
            &[injection("mw")],
            "wasi:http/handler",
            &Some("fp-a".to_string()),
            &mut cache,
        );
        assert_eq!(results.len(), 1);
        assert!(
            matches!(results[0], ContractResult::Error(_)),
            "expected Error, got {:?}",
            results[0]
        );
    }

    // -----------------------------------------------------------------------
    // OK: compatible fingerprints
    // -----------------------------------------------------------------------

    #[test]
    fn ok_when_fingerprints_match() {
        let mut cache = cache_with("mw", "wasi:http/handler", "fp-a");
        let results = validate_contract(
            &[injection("mw")],
            "wasi:http/handler",
            &Some("fp-a".to_string()),
            &mut cache,
        );
        assert_eq!(results.len(), 1);
        assert_eq!(results[0], ContractResult::Ok);
    }

    // -----------------------------------------------------------------------
    // Tier classification (`match_tier_interfaces`, `classify_tier_export`)
    // -----------------------------------------------------------------------

    /// Build an exports map containing one or more interface keys, each
    /// associated with a fingerprint-less stub. Useful for tier-detection
    /// tests that don't care about fingerprints.
    fn exports_for(ifaces: &[&str]) -> BTreeMap<String, ExportInfo> {
        let mut out = BTreeMap::new();
        for iface in ifaces {
            out.insert(
                iface.to_string(),
                ExportInfo {
                    source_instance: 0,
                    fingerprint: None,
                    ty: None,
                },
            );
        }
        out
    }

    #[test]
    fn match_tier_interfaces_picks_up_versioned_exports() {
        // Versioned export key + unversioned constant → semver-compatible match.
        let exports = exports_for(&["splicer:tier2/before@0.1.0", "splicer:tier2/after@0.1.7"]);
        let matched = match_tier_interfaces(&exports, TIER2_INTERFACES, TIER2_VERSION);
        assert_eq!(matched.len(), 2);
        assert!(matched.iter().any(|i| i == "splicer:tier2/before"));
        assert!(matched.iter().any(|i| i == "splicer:tier2/after"));
    }

    #[test]
    fn match_tier_interfaces_skips_unrelated_packages() {
        let exports = exports_for(&["other:pkg/iface", "wasi:http/handler@0.3.0"]);
        let matched = match_tier_interfaces(&exports, TIER2_INTERFACES, TIER2_VERSION);
        assert!(matched.is_empty());
    }

    #[test]
    fn multi_tier_export_is_rejected_with_clear_error() {
        // Middleware exports both tier-1 AND tier-2 → multi-tier rejection,
        // independent of whether the binary path resolves (the rejection
        // happens before adapter-eligibility checks).
        let mut cache = HashMap::new();
        cache.insert(
            "mw".to_string(),
            exports_for(&[
                &versioned_interface("splicer:tier1/before", TIER1_VERSION),
                &versioned_interface("splicer:tier2/after", TIER2_VERSION),
            ]),
        );
        let results = validate_contract(
            &[injection("mw")],
            "wasi:http/handler@0.3.0",
            &None,
            &mut cache,
        );
        assert_eq!(results.len(), 1);
        let ContractResult::Error(msg) = &results[0] else {
            panic!("expected Error, got {:?}", results[0]);
        };
        assert!(
            msg.contains("multiple tiers"),
            "error message should mention multi-tier rejection: {msg}"
        );
        assert!(
            msg.contains("tier 1") && msg.contains("tier 2"),
            "error message should name both tiers: {msg}"
        );
    }

    #[test]
    fn tier2_only_without_path_falls_through_to_warn() {
        // Tier-2 detection succeeds on the export side, but adapter
        // generation needs the binary path. Without a path we can't
        // confirm adapter-eligibility, so we surface a Warn rather
        // than mis-classifying.
        let mut cache = HashMap::new();
        cache.insert(
            "mw".to_string(),
            exports_for(&["splicer:tier2/before@0.1.0"]),
        );
        let results = validate_contract(
            &[injection("mw")],
            "wasi:http/handler@0.3.0",
            &None,
            &mut cache,
        );
        assert_eq!(results.len(), 1);
        assert!(
            matches!(results[0], ContractResult::Warn(_)),
            "expected Warn (no path), got {:?}",
            results[0]
        );
    }

    #[test]
    fn ok_when_both_fingerprints_none() {
        // If neither side has type info, compatible_fingerprints returns true → Ok.
        let mut cache = HashMap::new();
        let mut exports = BTreeMap::new();
        exports.insert(
            "wasi:http/handler".to_string(),
            ExportInfo {
                source_instance: 0,
                fingerprint: None,
                ty: None,
            },
        );
        cache.insert("mw".to_string(), exports);

        let results = validate_contract(&[injection("mw")], "wasi:http/handler", &None, &mut cache);
        assert_eq!(results.len(), 1);
        assert_eq!(results[0], ContractResult::Ok);
    }

    // -----------------------------------------------------------------------
    // WAT-based integration: discover_exports_from_bytes
    // -----------------------------------------------------------------------

    /// Middleware using the FromExports pattern produces a non-None fingerprint.
    #[test]
    fn discover_exports_from_from_exports_mw() {
        let wat = r#"(component
            (import "wasi:http/handler@0.3.0" (instance $host
                (export "handle" (func (param "req" u32) (result u32)))
            ))
            (alias export $host "handle" (func $f))
            (instance $out (export "handle" (func $f)))
            (export "wasi:http/handler@0.3.0" (instance $out))
        )"#;
        let bytes = wat::parse_str(wat).expect("failed to parse WAT");
        let exports = discover_exports_from_bytes(&bytes);

        let export = exports
            .get("wasi:http/handler@0.3.0")
            .expect("expected export for wasi:http/handler@0.3.0");
        assert!(
            export.fingerprint.is_some(),
            "expected fingerprint for FromExports middleware"
        );
    }

    /// Middleware that directly re-exports an imported instance produces a
    /// non-None fingerprint (RC-3 coverage).
    #[test]
    fn discover_exports_from_passthrough_mw() {
        let wat = r#"(component
            (import "wasi:http/handler@0.3.0" (instance $handler
                (export "handle" (func (param "req" u32) (result u32)))
            ))
            (export "wasi:http/handler@0.3.0" (instance $handler))
        )"#;
        let bytes = wat::parse_str(wat).expect("failed to parse WAT");
        let exports = discover_exports_from_bytes(&bytes);

        let export = exports
            .get("wasi:http/handler@0.3.0")
            .expect("expected export for wasi:http/handler@0.3.0");
        assert!(
            export.fingerprint.is_some(),
            "expected fingerprint for import-reexport middleware"
        );
    }

    /// A compatible WAT middleware (same signature as chain) validates as Ok.
    #[test]
    fn ok_result_for_compatible_wat_middleware() {
        // Chain component exports "handle" (param u32) -> u32
        let chain_wat = r#"(component
            (import "wasi:http/handler@0.3.0" (instance $host
                (export "handle" (func (param "req" u32) (result u32)))
            ))
            (alias export $host "handle" (func $f))
            (instance $out (export "handle" (func $f)))
            (export "wasi:http/handler@0.3.0" (instance $out))
        )"#;
        // Middleware with the same signature
        let mw_wat = r#"(component
            (import "wasi:http/handler@0.3.0" (instance $handler
                (export "handle" (func (param "req" u32) (result u32)))
            ))
            (export "wasi:http/handler@0.3.0" (instance $handler))
        )"#;

        let chain_bytes = wat::parse_str(chain_wat).expect("failed to parse chain WAT");
        let mw_bytes = wat::parse_str(mw_wat).expect("failed to parse middleware WAT");

        let chain_exports = discover_exports_from_bytes(&chain_bytes);
        let chain_fp = chain_exports
            .get("wasi:http/handler@0.3.0")
            .and_then(|e| e.fingerprint.clone());

        // Pre-populate cache with the middleware's discovered exports
        let mw_exports = discover_exports_from_bytes(&mw_bytes);
        let mut cache = HashMap::new();
        cache.insert("mw".to_string(), mw_exports);

        let inj = Injection {
            name: "mw".to_string(),
            adapter_info: None,
            builtin: None,
            path: None,
        };
        let results = validate_contract(&[inj], "wasi:http/handler@0.3.0", &chain_fp, &mut cache);
        assert_eq!(results.len(), 1);
        assert_eq!(results[0], ContractResult::Ok);
    }

    /// An incompatible WAT middleware (different signature) validates as Error.
    #[test]
    fn error_result_for_incompatible_wat_middleware() {
        // Chain: (param u32) -> u32
        let chain_wat = r#"(component
            (import "wasi:http/handler@0.3.0" (instance $host
                (export "handle" (func (param "req" u32) (result u32)))
            ))
            (alias export $host "handle" (func $f))
            (instance $out (export "handle" (func $f)))
            (export "wasi:http/handler@0.3.0" (instance $out))
        )"#;
        // Incompatible middleware: different param type
        let mw_wat = r#"(component
            (import "wasi:http/handler@0.3.0" (instance $handler
                (export "handle" (func (param "req" string) (result u32)))
            ))
            (export "wasi:http/handler@0.3.0" (instance $handler))
        )"#;

        let chain_bytes = wat::parse_str(chain_wat).expect("failed to parse chain WAT");
        let mw_bytes = wat::parse_str(mw_wat).expect("failed to parse middleware WAT");

        let chain_exports = discover_exports_from_bytes(&chain_bytes);
        let chain_fp = chain_exports
            .get("wasi:http/handler@0.3.0")
            .and_then(|e| e.fingerprint.clone());

        let mw_exports = discover_exports_from_bytes(&mw_bytes);
        let mut cache = HashMap::new();
        cache.insert("mw".to_string(), mw_exports);

        let inj = Injection {
            name: "mw".to_string(),
            adapter_info: None,
            builtin: None,
            path: None,
        };
        let results = validate_contract(&[inj], "wasi:http/handler@0.3.0", &chain_fp, &mut cache);
        assert_eq!(results.len(), 1);
        assert!(
            matches!(results[0], ContractResult::Error(_)),
            "expected Error for incompatible middleware"
        );
    }

    #[test]
    fn mixed_results_for_multiple_injections() {
        // mw-ok: matching fingerprint → Ok
        // mw-bad: mismatched fingerprint → Error
        // mw-unknown: not in cache, no path → Warn
        let injections = vec![
            injection("mw-ok"),
            injection("mw-bad"),
            injection("mw-unknown"),
        ];
        let mut cache = HashMap::new();
        cache.insert("mw-ok".to_string(), {
            let mut m = BTreeMap::new();
            m.insert(
                "wasi:http/handler".to_string(),
                export_with_fingerprint("fp-a"),
            );
            m
        });
        cache.insert("mw-bad".to_string(), {
            let mut m = BTreeMap::new();
            m.insert(
                "wasi:http/handler".to_string(),
                export_with_fingerprint("fp-b"),
            );
            m
        });
        // mw-unknown: not inserted → discovery runs, returns empty (path: None)

        let results = validate_contract(
            &injections,
            "wasi:http/handler",
            &Some("fp-a".to_string()),
            &mut cache,
        );
        assert_eq!(results.len(), 3);
        assert_eq!(results[0], ContractResult::Ok);
        assert!(matches!(results[1], ContractResult::Error(_)));
        assert!(matches!(results[2], ContractResult::Warn(_)));
    }
}
