use nyash_rust::ast::{ASTNode, LiteralValue, Span};
use nyash_rust::mir::{
    BasicBlock, BasicBlockId, ConstValue, EffectMask, FunctionSignature, MirBuilder, MirFunction,
    MirInstruction, MirPrinter, MirType, MirVerifier, VerificationError,
};

#[test]
fn test_valid_function_verification() {
    let signature = FunctionSignature {
        name: "test".to_string(),
        params: vec![],
        return_type: MirType::Void,
        effects: EffectMask::PURE,
    };

    let entry_block = BasicBlockId::new(0);
    let function = MirFunction::new(signature, entry_block);

    let mut verifier = MirVerifier::new();
    let result = verifier.verify_function(&function);

    assert!(result.is_ok(), "Valid function should pass verification");
}

#[test]
fn test_undefined_value_detection() {
    // Placeholder: Define a minimal function without uses; this test is a scaffold.
    let signature = FunctionSignature {
        name: "undef_sanity".to_string(),
        params: vec![],
        return_type: MirType::Void,
        effects: EffectMask::PURE,
    };
    let entry_block = BasicBlockId::new(0);
    let function = MirFunction::new(signature, entry_block);
    let mut verifier = MirVerifier::new();
    let _ = verifier.verify_function(&function).unwrap();
}

#[test]
fn test_if_merge_uses_phi_not_predecessor() {
    // Program:
    // if true { result = "A" } else { result = "B" }
    // result
    let ast = ASTNode::Program {
        statements: vec![
            ASTNode::Local {
                variables: vec!["result".to_string()],
                initial_values: vec![None],
                span: Span::unknown(),
            },
            ASTNode::If {
                condition: Box::new(ASTNode::Literal {
                    value: LiteralValue::Bool(true),
                    span: Span::unknown(),
                }),
                then_body: vec![ASTNode::Assignment {
                    target: Box::new(ASTNode::Variable {
                        name: "result".to_string(),
                        span: Span::unknown(),
                    }),
                    value: Box::new(ASTNode::Literal {
                        value: LiteralValue::String("A".to_string()),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }],
                else_body: Some(vec![ASTNode::Assignment {
                    target: Box::new(ASTNode::Variable {
                        name: "result".to_string(),
                        span: Span::unknown(),
                    }),
                    value: Box::new(ASTNode::Literal {
                        value: LiteralValue::String("B".to_string()),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }]),
                span: Span::unknown(),
            },
            ASTNode::Variable {
                name: "result".to_string(),
                span: Span::unknown(),
            },
        ],
        span: Span::unknown(),
    };

    let mut builder = MirBuilder::new();
    let module = builder.build_module(ast).expect("build mir");

    // Verify: should be OK (no MergeUsesPredecessorValue)
    let mut verifier = MirVerifier::new();
    let res = verifier.verify_module(&module);
    if let Err(errs) = &res {
        eprintln!("Verifier errors: {:?}", errs);
    }
    assert!(res.is_ok(), "MIR should pass merge-phi verification");

    // Optional: ensure printer shows a phi in merge and ret returns a defined value
    let printer = MirPrinter::verbose();
    let mir_text = printer.print_module(&module);
    assert!(
        mir_text.contains("phi"),
        "Printed MIR should contain a phi in merge block\n{}",
        mir_text
    );
}

#[test]
fn test_merge_use_before_phi_detected() {
    // Construct a function with a bad merge use (no phi)
    let signature = FunctionSignature {
        name: "merge_bad".to_string(),
        params: vec![],
        return_type: MirType::String,
        effects: EffectMask::PURE,
    };

    let entry = BasicBlockId::new(0);
    let mut f = MirFunction::new(signature, entry);

    let then_bb = BasicBlockId::new(1);
    let else_bb = BasicBlockId::new(2);
    let merge_bb = BasicBlockId::new(3);

    let cond = f.next_value_id(); // %0
    {
        let b0 = f.get_block_mut(entry).unwrap();
        b0.add_instruction(MirInstruction::Const {
            dst: cond,
            value: ConstValue::Bool(true),
        });
        b0.add_instruction(MirInstruction::Branch {
            condition: cond,
            then_bb,
            else_bb,
            then_edge_args: None,
            else_edge_args: None,
        });
    }

    let v1 = f.next_value_id(); // %1
    let mut b1 = BasicBlock::new(then_bb);
    b1.add_instruction(MirInstruction::Const {
        dst: v1,
        value: ConstValue::String("A".to_string()),
    });
    b1.add_instruction(MirInstruction::Jump {
        target: merge_bb,
        edge_args: None,
    });
    f.add_block(b1);

    let v2 = f.next_value_id(); // %2
    let mut b2 = BasicBlock::new(else_bb);
    b2.add_instruction(MirInstruction::Const {
        dst: v2,
        value: ConstValue::String("B".to_string()),
    });
    b2.add_instruction(MirInstruction::Jump {
        target: merge_bb,
        edge_args: None,
    });
    f.add_block(b2);

    let mut b3 = BasicBlock::new(merge_bb);
    // Illegal: directly use v1 from predecessor
    b3.add_instruction(MirInstruction::Return { value: Some(v1) });
    f.add_block(b3);

    f.update_cfg();

    let mut verifier = MirVerifier::new();
    let res = verifier.verify_function(&f);
    assert!(
        res.is_err(),
        "Verifier should error on merge use without phi"
    );
    let errs = res.err().unwrap();
    assert!(
        errs.iter().any(|e| matches!(
            e,
            VerificationError::MergeUsesPredecessorValue { .. }
                | VerificationError::DominatorViolation { .. }
        )),
        "Expected merge/dominator error, got: {:?}",
        errs
    );
}

#[test]
fn test_loop_phi_normalization() {
    // Program:
    // local i = 0
    // loop(i < 1) { i = i + 1 }
    // i
    let ast = ASTNode::Program {
        statements: vec![
            ASTNode::Local {
                variables: vec!["i".to_string()],
                initial_values: vec![Some(Box::new(ASTNode::Literal {
                    value: LiteralValue::Integer(0),
                    span: Span::unknown(),
                }))],
                span: Span::unknown(),
            },
            ASTNode::Loop {
                condition: Box::new(ASTNode::BinaryOp {
                    operator: nyash_rust::ast::BinaryOperator::Less,
                    left: Box::new(ASTNode::Variable {
                        name: "i".to_string(),
                        span: Span::unknown(),
                    }),
                    right: Box::new(ASTNode::Literal {
                        value: LiteralValue::Integer(1),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }),
                body: vec![ASTNode::Assignment {
                    target: Box::new(ASTNode::Variable {
                        name: "i".to_string(),
                        span: Span::unknown(),
                    }),
                    value: Box::new(ASTNode::BinaryOp {
                        operator: nyash_rust::ast::BinaryOperator::Add,
                        left: Box::new(ASTNode::Variable {
                            name: "i".to_string(),
                            span: Span::unknown(),
                        }),
                        right: Box::new(ASTNode::Literal {
                            value: LiteralValue::Integer(1),
                            span: Span::unknown(),
                        }),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }],
                span: Span::unknown(),
            },
            ASTNode::Variable {
                name: "i".to_string(),
                span: Span::unknown(),
            },
        ],
        span: Span::unknown(),
    };

    let mut builder = MirBuilder::new();
    let module = builder.build_module(ast).expect("build mir");

    // Verify SSA/dominance: should pass
    let mut verifier = MirVerifier::new();
    let res = verifier.verify_module(&module);
    if let Err(errs) = &res {
        eprintln!("Verifier errors: {:?}", errs);
    }
    assert!(
        res.is_ok(),
        "MIR loop with phi normalization should pass verification"
    );

    // Ensure phi is printed (header phi for variable i)
    let printer = MirPrinter::verbose();
    let mir_text = printer.print_module(&module);
    assert!(
        mir_text.contains("phi"),
        "Printed MIR should contain a phi for loop header\n{}",
        mir_text
    );
}

#[test]
fn test_loop_nested_if_phi() {
    // Program:
    // local x = 0
    // loop(i < 1) { if true { x = 1 } else { x = 2 }; i = i + 1 }
    // x
    let ast = ASTNode::Program {
        statements: vec![
            ASTNode::Local {
                variables: vec!["i".to_string()],
                initial_values: vec![Some(Box::new(ASTNode::Literal {
                    value: LiteralValue::Integer(0),
                    span: Span::unknown(),
                }))],
                span: Span::unknown(),
            },
            ASTNode::Local {
                variables: vec!["x".to_string()],
                initial_values: vec![Some(Box::new(ASTNode::Literal {
                    value: LiteralValue::Integer(0),
                    span: Span::unknown(),
                }))],
                span: Span::unknown(),
            },
            ASTNode::Loop {
                condition: Box::new(ASTNode::BinaryOp {
                    operator: nyash_rust::ast::BinaryOperator::Less,
                    left: Box::new(ASTNode::Variable {
                        name: "i".to_string(),
                        span: Span::unknown(),
                    }),
                    right: Box::new(ASTNode::Literal {
                        value: LiteralValue::Integer(1),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                }),
                body: vec![
                    ASTNode::If {
                    condition: Box::new(ASTNode::BinaryOp {
                        operator: nyash_rust::ast::BinaryOperator::Equal,
                        left: Box::new(ASTNode::Variable {
                            name: "i".to_string(),
                            span: Span::unknown(),
                        }),
                        right: Box::new(ASTNode::Literal {
                            value: LiteralValue::Integer(0),
                            span: Span::unknown(),
                        }),
                        span: Span::unknown(),
                    }),
                    then_body: vec![ASTNode::Assignment {
                        target: Box::new(ASTNode::Variable {
                            name: "x".to_string(),
                            span: Span::unknown(),
                        }),
                        value: Box::new(ASTNode::BinaryOp {
                            operator: nyash_rust::ast::BinaryOperator::Add,
                            left: Box::new(ASTNode::Variable {
                                name: "x".to_string(),
                                span: Span::unknown(),
                            }),
                            right: Box::new(ASTNode::Literal {
                                value: LiteralValue::Integer(1),
                                span: Span::unknown(),
                            }),
                            span: Span::unknown(),
                        }),
                        span: Span::unknown(),
                    }],
                    else_body: Some(vec![ASTNode::Assignment {
                        target: Box::new(ASTNode::Variable {
                            name: "x".to_string(),
                            span: Span::unknown(),
                        }),
                        value: Box::new(ASTNode::BinaryOp {
                            operator: nyash_rust::ast::BinaryOperator::Add,
                            left: Box::new(ASTNode::Variable {
                                name: "x".to_string(),
                                span: Span::unknown(),
                            }),
                            right: Box::new(ASTNode::Literal {
                                value: LiteralValue::Integer(2),
                                span: Span::unknown(),
                            }),
                            span: Span::unknown(),
                        }),
                        span: Span::unknown(),
                    }]),
                    span: Span::unknown(),
                },
                ASTNode::Assignment {
                    target: Box::new(ASTNode::Variable {
                        name: "i".to_string(),
                        span: Span::unknown(),
                    }),
                    value: Box::new(ASTNode::BinaryOp {
                        operator: nyash_rust::ast::BinaryOperator::Add,
                        left: Box::new(ASTNode::Variable {
                            name: "i".to_string(),
                            span: Span::unknown(),
                        }),
                        right: Box::new(ASTNode::Literal {
                            value: LiteralValue::Integer(1),
                            span: Span::unknown(),
                        }),
                        span: Span::unknown(),
                    }),
                    span: Span::unknown(),
                },
                ],
                span: Span::unknown(),
            },
            ASTNode::Variable {
                name: "x".to_string(),
                span: Span::unknown(),
            },
        ],
        span: Span::unknown(),
    };

    let mut builder = MirBuilder::new();
    let module = builder.build_module(ast).expect("build mir");

    let mut verifier = MirVerifier::new();
    let res = verifier.verify_module(&module);
    if let Err(errs) = &res {
        eprintln!("Verifier errors: {:?}", errs);
    }
    assert!(
        res.is_ok(),
        "Nested if in loop should pass verification with proper phis"
    );

    let printer = MirPrinter::verbose();
    let mir_text = printer.print_module(&module);
    assert!(
        mir_text.contains("phi"),
        "Printed MIR should contain phi nodes for nested if/loop\n{}",
        mir_text
    );
}
