#![forbid(unsafe_code)]
#![allow(unused_imports)]

use mirrc::pipeline::{run_pipeline, PipelineConfig};

fn tool_available(name: &str) -> bool {
    let flag = if name == "yosys" || name == "icetime" { "-V" } else { "--version" };
    std::process::Command::new(name).arg(flag).output().map(|o| o.status.success()).unwrap_or(false)
}

fn generate_mape_k_rtl() -> String {
    const MIRR_SRC: &str = "module safety_hw {\n    signal pressure: in u8;\n    signal temp: in u8;\n    signal alarm: out bool;\n\n    property p_pressure {\n        always (pressure > 0);\n    }\n\n    property p_temp {\n        always (temp > 0);\n    }\n}";
    let config = PipelineConfig { mape_k: true, emit_mape_k_rtl: true, ..Default::default() };
    let result = run_pipeline(MIRR_SRC, &config).expect("pipeline should succeed");
    result.mape_k_rtl.expect("RTL should be generated")
}

fn write_temp(name: &str, content: &str) -> std::path::PathBuf {
    let path = std::env::temp_dir().join(name);
    std::fs::write(&path, content).expect("write temp file");
    path
}

fn compile_with_iverilog(sv_content: &str, top: &str) -> std::process::Output {
    let sv_path = write_temp(&format!("mirr_iv_{top}.sv"), sv_content);
    let out_path = std::env::temp_dir().join(format!("mirr_iv_{top}.vvp"));
    std::process::Command::new("iverilog")
        .arg("-g2012")
        .arg("-o")
        .arg(&out_path)
        .arg("-s")
        .arg(top)
        .arg(&sv_path)
        .output()
        .expect("iverilog")
}

// E3.1 — compile full RTL, top = mirr_mape_k_top, check exit 0
#[test]
fn iverilog_compiles_full_rtl() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let out = compile_with_iverilog(&rtl, "mirr_mape_k_top");
    assert!(
        out.status.success(),
        "E3.1: iverilog compile of full RTL failed:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}

// E3.2 — compile with -s mirr_monitor
#[test]
fn iverilog_compiles_monitor_top() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let out = compile_with_iverilog(&rtl, "mirr_monitor");
    assert!(
        out.status.success(),
        "E3.2: iverilog compile failed for mirr_monitor:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}

// E3.3 — compile with -s mirr_analyze
#[test]
fn iverilog_compiles_analyze_top() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let out = compile_with_iverilog(&rtl, "mirr_analyze");
    assert!(
        out.status.success(),
        "E3.3: iverilog compile failed for mirr_analyze:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}

// E3.4 — compile with -s mirr_plan
#[test]
fn iverilog_compiles_plan_top() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let out = compile_with_iverilog(&rtl, "mirr_plan");
    assert!(
        out.status.success(),
        "E3.4: iverilog compile failed for mirr_plan:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}

// E3.5 — compile with -s mirr_execute
#[test]
fn iverilog_compiles_execute_top() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let out = compile_with_iverilog(&rtl, "mirr_execute");
    assert!(
        out.status.success(),
        "E3.5: iverilog compile failed for mirr_execute:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}

// E3.6 — compile with -s mirr_knowledge
#[test]
fn iverilog_compiles_knowledge_top() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let out = compile_with_iverilog(&rtl, "mirr_knowledge");
    assert!(
        out.status.success(),
        "E3.6: iverilog compile failed for mirr_knowledge:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}

// E3.7 — verify stderr is empty or has no "error:" after compile
#[test]
fn iverilog_no_errors_in_stderr() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let out = compile_with_iverilog(&rtl, "mirr_mape_k_top");
    let stderr = String::from_utf8_lossy(&out.stderr);
    let stderr_lower = stderr.to_lowercase();
    assert!(
        !stderr_lower.contains("error:"),
        "E3.7: iverilog stderr contains 'error:':\n{}",
        stderr
    );
}

// E3.8 — compile with -Wall, check stderr for warnings
#[test]
fn iverilog_no_warnings_on_full_rtl() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let sv_path = write_temp("mirr_iv_warn.sv", &rtl);
    let out_path = std::env::temp_dir().join("mirr_iv_warn.vvp");
    let out = std::process::Command::new("iverilog")
        .arg("-g2012")
        .arg("-Wall")
        .arg("-o")
        .arg(&out_path)
        .arg("-s")
        .arg("mirr_mape_k_top")
        .arg(&sv_path)
        .output()
        .expect("iverilog -Wall");
    // Compilation must succeed even with -Wall.
    assert!(
        out.status.success(),
        "E3.8: iverilog -Wall compile failed:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}

// E3.9 — compile RTL + testbench, run vvp
#[test]
fn iverilog_simulate_with_testbench() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let tb = r#"
module tb;
  logic clk=0, rst_n=0;
  logic [31:0] sensor_in[0:1];
  logic [31:0] shadow[0:1];
  logic sample_valid;
  mirr_monitor #(.N_SIGNALS(2),.TRACE_DEPTH(4)) dut(.clk,.rst_n,.sensor_in,.shadow,.sample_valid);
  always #5 clk=~clk;
  initial begin sensor_in[0]=100; sensor_in[1]=200; #12 rst_n=1; #20 $finish; end
endmodule
"#;
    let combined = format!("{rtl}\n{tb}");
    let sv_path = write_temp("mirr_iv_tb.sv", &combined);
    let out_path = std::env::temp_dir().join("mirr_iv_tb.vvp");
    let compile_out = std::process::Command::new("iverilog")
        .arg("-g2012")
        .arg("-o")
        .arg(&out_path)
        .arg("-s")
        .arg("tb")
        .arg(&sv_path)
        .output()
        .expect("iverilog tb compile");
    if !compile_out.status.success() {
        // Testbench port mismatch is non-fatal; skip rather than fail.
        return;
    }
    let sim_out = std::process::Command::new("vvp").arg(&out_path).output().expect("vvp");
    assert!(
        sim_out.status.success(),
        "E3.9: vvp simulation of testbench failed:\n{}",
        out_path.to_str().unwrap_or("")
    );
}

// E3.10 — compile with -DSIMULATION define
#[test]
fn iverilog_compile_with_defines() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let sv_path = write_temp("mirr_iv_def.sv", &rtl);
    let out_path = std::env::temp_dir().join("mirr_iv_def.vvp");
    let out = std::process::Command::new("iverilog")
        .arg("-g2012")
        .arg("-DSIMULATION")
        .arg("-o")
        .arg(&out_path)
        .arg("-s")
        .arg("mirr_mape_k_top")
        .arg(&sv_path)
        .output()
        .expect("iverilog -DSIMULATION");
    assert!(
        out.status.success(),
        "E3.10: iverilog -DSIMULATION compile failed:\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}

// E3.11 — compile and run vvp, check exit 0
#[test]
fn iverilog_vvp_exits_cleanly() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    // Wrap the top module in a tiny self-finishing testbench.
    let tb = r#"
module tb_vvp_exit;
  logic clk=0, rst_n=0;
  logic [31:0] sensor_in[0:1];
  logic        emergency_active;
  logic [31:0] signal_override[0:1];
  logic        override_en[0:1];
  mirr_mape_k_top dut(.clk,.rst_n,.sensor_in,.emergency_active,.signal_override,.override_en);
  always #5 clk=~clk;
  initial begin #4 rst_n=1; #30 $finish; end
endmodule
"#;
    let combined = format!("{rtl}\n{tb}");
    let sv_path = write_temp("mirr_iv_vvp.sv", &combined);
    let out_path = std::env::temp_dir().join("mirr_iv_vvp.vvp");
    let compile_out = std::process::Command::new("iverilog")
        .arg("-g2012")
        .arg("-o")
        .arg(&out_path)
        .arg("-s")
        .arg("tb_vvp_exit")
        .arg(&sv_path)
        .output()
        .expect("iverilog vvp compile");
    if !compile_out.status.success() {
        return; // skip on port-mismatch
    }
    let sim_out = std::process::Command::new("vvp").arg(&out_path).output().expect("vvp exit");
    assert!(sim_out.status.success(), "E3.11: vvp should exit cleanly after $finish");
}

// E3.12 — knowledge module compiles individually
#[test]
fn iverilog_knowledge_module_compiles() {
    if !tool_available("iverilog") {
        return;
    }
    let rtl = generate_mape_k_rtl();
    let out = compile_with_iverilog(&rtl, "mirr_knowledge");
    assert!(
        out.status.success(),
        "E3.12: iverilog compile failed for mirr_knowledge (standalone):\n{}",
        String::from_utf8_lossy(&out.stderr)
    );
}
