use super::*;
use crate::ast::{Span, ASTNode, BinaryOperator, LiteralValue};

fn str_lit(s: &str) -> ASTNode {
    ASTNode::Literal {
        value: LiteralValue::String(s.to_string()),
        span: Span::unknown(),
    }
}

fn eq(a: ASTNode, b: ASTNode) -> ASTNode {
    ASTNode::BinaryOp {
        operator: BinaryOperator::Equal,
        left: Box::new(a),
        right: Box::new(b),
        span: Span::unknown(),
    }
}

fn assign_x(num: i64) -> ASTNode {
    ASTNode::Assignment {
        target: Box::new(ASTNode::Variable {
            name: "x".to_string(),
            span: Span::unknown(),
        }),
        value: Box::new(ASTNode::Literal {
            value: LiteralValue::Integer(num),
            span: Span::unknown(),
        }),
        span: Span::unknown(),
    }
}

#[test]
fn build_step_tree_if_only_nested_if_is_structural() {
    // Equivalent shape to Phase103 "if-only merge" fixture:
    //
    // local x = 0
    // if "x" == "x" { if "y" == "z" { x=1 } else { x=2 } } else { x=3 }
    // print(x)
    let ast = vec![
        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::If {
            condition: Box::new(eq(str_lit("x"), str_lit("x"))),
            then_body: vec![ASTNode::If {
                condition: Box::new(eq(str_lit("y"), str_lit("z"))),
                then_body: vec![assign_x(1)],
                else_body: Some(vec![assign_x(2)]),
                span: Span::unknown(),
            }],
            else_body: Some(vec![assign_x(3)]),
            span: Span::unknown(),
        },
        ASTNode::Print {
            expression: Box::new(ASTNode::Variable {
                name: "x".to_string(),
                span: Span::unknown(),
            }),
            span: Span::unknown(),
        },
    ];

    let tree = StepTreeBuilderBox::build_from_block(&ast);
    assert!(tree.features.has_if);
    assert!(!tree.features.has_loop);
    assert_eq!(tree.features.max_if_depth, 2);
    assert_eq!(tree.contract.exits.len(), 0);
    assert!(tree.contract.writes.contains("x"));
    assert!(tree.contract.required_caps.contains(&StepCapability::If));
    assert!(tree.contract.required_caps.contains(&StepCapability::NestedIf));

    let basis = tree.signature_basis_string();
    assert_eq!(
        basis,
        "kinds=Block,Stmt(local(x)),If,Block,If,Block,Stmt(assign(x)),Block,Stmt(assign(x)),Block,Stmt(assign(x)),Stmt(print);exits=;writes=x;reads=;caps=If,NestedIf;conds=(lit:str:x == lit:str:x)|(lit:str:y == lit:str:z)"
    );

    let tree2 = StepTreeBuilderBox::build_from_block(&ast);
    assert_eq!(tree.signature, tree2.signature);

    match tree.root {
        StepNode::Block(nodes) => {
            assert_eq!(nodes.len(), 3);
            match &nodes[1] {
                StepNode::If { then_branch, cond_ast, .. } => {
                    // cond_ast should be populated
                    assert!(matches!(&cond_ast.0.as_ref(), ASTNode::BinaryOp { .. }));

                    match &**then_branch {
                        StepNode::Block(inner_nodes) => match &inner_nodes[0] {
                            StepNode::If { cond_ast: inner_cond_ast, .. } => {
                                // inner cond_ast should also be populated
                                assert!(matches!(&inner_cond_ast.0.as_ref(), ASTNode::BinaryOp { .. }));
                            }
                            other => panic!("expected nested If, got {other:?}"),
                        },
                        other => panic!("expected Block in then_branch, got {other:?}"),
                    }
                }
                other => panic!("expected If at index 1, got {other:?}"),
            }
        }
        other => panic!("expected root Block, got {other:?}"),
    }
}

#[test]
fn step_tree_cond_ast_is_populated() {
    // Phase 119: cond_ast should hold AST reference.
    let ast = vec![ASTNode::If {
        condition: Box::new(eq(str_lit("a"), str_lit("b"))),
        then_body: vec![assign_x(1)],
        else_body: None,
        span: Span::unknown(),
    }];

    let tree = StepTreeBuilderBox::build_from_block(&ast);

    match &tree.root {
        StepNode::Block(nodes) => match &nodes[0] {
            StepNode::If { cond_ast, .. } => {
                // cond_ast should be populated with BinaryOp
                assert!(matches!(&cond_ast.0.as_ref(), ASTNode::BinaryOp { .. }));
            }
            other => panic!("expected If, got {other:?}"),
        },
        other => panic!("expected root Block, got {other:?}"),
    }
}

#[test]
fn step_tree_signature_is_stable_with_cond_ast() {
    // Phase 119: cond_ast should NOT affect signature stability.
    // Signature is based on cond_sig (AstSummary), not cond_ast.
    let ast = vec![
        ASTNode::Loop {
            condition: Box::new(ASTNode::Literal {
                value: LiteralValue::Bool(true),
                span: Span::unknown(),
            }),
            body: vec![assign_x(1)],
            span: Span::unknown(),
        },
    ];

    let tree1 = StepTreeBuilderBox::build_from_block(&ast);
    let tree2 = StepTreeBuilderBox::build_from_block(&ast);

    // Signature should be identical (deterministic)
    assert_eq!(tree1.signature, tree2.signature);

    // cond_ast should be populated
    match &tree1.root {
        StepNode::Block(nodes) => match &nodes[0] {
            StepNode::Loop { cond_ast, .. } => {
                assert!(matches!(&cond_ast.0.as_ref(), ASTNode::Literal { .. }));
            }
            other => panic!("expected Loop, got {other:?}"),
        },
        other => panic!("expected root Block, got {other:?}"),
    }
}

#[test]
fn contract_extracts_loop_exits_and_writes_minimal() {
    fn var(name: &str) -> ASTNode {
        ASTNode::Variable {
            name: name.to_string(),
            span: Span::unknown(),
        }
    }
    fn int_lit(v: i64) -> ASTNode {
        ASTNode::Literal {
            value: LiteralValue::Integer(v),
            span: Span::unknown(),
        }
    }
    fn bin(op: BinaryOperator, lhs: ASTNode, rhs: ASTNode) -> ASTNode {
        ASTNode::BinaryOp {
            operator: op,
            left: Box::new(lhs),
            right: Box::new(rhs),
            span: Span::unknown(),
        }
    }
    fn assign(name: &str, value: ASTNode) -> ASTNode {
        ASTNode::Assignment {
            target: Box::new(var(name)),
            value: Box::new(value),
            span: Span::unknown(),
        }
    }

    // local i=0; local x=0;
    // loop(i < 3) { x = x + 1; if x == 2 { break } i = i + 1 }
    let ast = vec![
        ASTNode::Local {
            variables: vec!["i".to_string()],
            initial_values: vec![Some(Box::new(int_lit(0)))],
            span: Span::unknown(),
        },
        ASTNode::Local {
            variables: vec!["x".to_string()],
            initial_values: vec![Some(Box::new(int_lit(0)))],
            span: Span::unknown(),
        },
        ASTNode::Loop {
            condition: Box::new(bin(BinaryOperator::Less, var("i"), int_lit(3))),
            body: vec![
                assign("x", bin(BinaryOperator::Add, var("x"), int_lit(1))),
                ASTNode::If {
                    condition: Box::new(bin(BinaryOperator::Equal, var("x"), int_lit(2))),
                    then_body: vec![ASTNode::Break { span: Span::unknown() }],
                    else_body: None,
                    span: Span::unknown(),
                },
                assign("i", bin(BinaryOperator::Add, var("i"), int_lit(1))),
            ],
            span: Span::unknown(),
        },
    ];

    let tree = StepTreeBuilderBox::build_from_block(&ast);
    assert!(tree.features.has_loop);
    assert!(tree.contract.exits.contains(&ExitKind::Break));
    assert!(tree.contract.writes.contains("i"));
    assert!(tree.contract.writes.contains("x"));
    assert!(tree.contract.required_caps.contains(&StepCapability::Loop));
    assert!(tree.contract.required_caps.contains(&StepCapability::If));
}
