use gust_lang::{RustCodegen, parse_program};

#[test]
fn async_effect_and_handler_generate_async_rust() {
    let source = r#"
machine Payments {
    state Pending
    state Done(receipt: String)

    transition charge: Pending -> Done

    async effect process() -> String

    async on charge() {
        let receipt = perform process();
        goto Done(receipt);
    }
}
"#;

    let program = parse_program(source).expect("source should parse");
    let generated = RustCodegen::new().generate(&program);

    assert!(generated.contains("async fn process(&self) -> String;"));
    assert!(generated.contains("pub async fn charge("));
    assert!(generated.contains("effects.process().await"));
}

#[test]
fn enum_and_match_generate_rust_enum_and_match() {
    let source = r#"
enum Status {
    Pending,
    Done(String),
}

machine Tracker {
    state Idle(status: Status)
    state Finished(msg: String)

    transition finish: Idle -> Finished

    on finish() {
        match status {
            Status::Done(msg) => { goto Finished(msg); }
            _ => { goto Finished("unknown"); }
        }
    }
}
"#;

    let program = parse_program(source).expect("source should parse");
    let generated = RustCodegen::new().generate(&program);

    assert!(generated.contains("pub enum Status {"));
    assert!(generated.contains("Pending,"));
    assert!(generated.contains("Done(String),"));
    assert!(generated.contains("match status {"));
}

#[test]
fn test_ctx_field_rewrite_and_borrows() {
    let source = r#"
type Order {
    id: String,
    items: Vec<String>,
}
type Money {
    cents: i64,
}
machine Processor {
    state Pending(order: Order)
    state Done(order: Order, total: Money)
    state Failed(reason: String)

    transition process: Pending -> Done | Failed

    effect calculate_total(order: Order) -> Money

    on process(ctx: ProcessCtx) {
        let total = perform calculate_total(ctx.order);
        if total.cents > 0 {
            goto Done(ctx.order, total);
        } else {
            goto Failed("bad total");
        }
    }
}
"#;
    let program = parse_program(source).expect("should parse");
    let generated = RustCodegen::new().generate(&program);

    // Bug 1: ctx.field must be rewritten to direct field access
    assert!(
        !generated.contains("ctx.order"),
        "ctx.field should be rewritten to field"
    );
    assert!(
        !generated.contains("ctx: ProcessCtx"),
        "ctx param should not appear in method sig"
    );

    // Should use clone match for owned destructuring
    assert!(
        generated.contains("match self.state.clone()"),
        "should clone state for owned access"
    );

    // Bug 5: perform args must be passed by reference
    assert!(
        generated.contains("effects.calculate_total(&"),
        "perform args should be references"
    );

    // Bug 4: no unnecessary parens in if condition
    assert!(
        !generated.contains("if (total"),
        "if condition should not have outer parens"
    );
    assert!(
        generated.contains("if total.cents > 0"),
        "if condition should be bare"
    );
}

#[test]
fn test_enum_path_in_expression() {
    let source = r#"
enum Stage { Build, Test, Deploy }
machine Pipeline {
    state Waiting(stage: Stage)
    state Running(stage: Stage)
    transition advance: Waiting -> Running
    on advance(ctx: AdvanceCtx) {
        goto Running(Stage::Build);
    }
}
"#;
    let program = parse_program(source).expect("should parse with enum path in expression");
    let generated = RustCodegen::new().generate(&program);
    assert!(
        generated.contains("Stage::Build"),
        "enum path should appear in generated Rust"
    );
}

#[test]
fn test_generated_rust_structural_validity() {
    let source = r#"
type Item { name: String, price: i64 }
machine Cart {
    state Empty
    state HasItems(items: Vec<Item>, total: i64)
    state CheckedOut(receipt: String)

    transition add_item: Empty -> HasItems
    transition checkout: HasItems -> CheckedOut

    effect compute_receipt(items: Vec<Item>) -> String

    on checkout(ctx: CheckoutCtx) {
        let receipt = perform compute_receipt(ctx.items);
        goto CheckedOut(receipt);
    }
}
"#;
    let program = parse_program(source).expect("should parse");
    let generated = RustCodegen::new().generate(&program);

    // Structural checks that would cause compilation failures:
    // 1. No undefined variables (ctx should be rewritten)
    assert!(!generated.contains("ctx."), "no ctx references");
    // 2. No type-unknown params in signatures
    assert!(
        !generated.contains("CheckoutCtx"),
        "no phantom types in sigs"
    );
    // 3. Proper match form
    assert!(
        generated.contains("match self.state.clone()"),
        "owned match"
    );
    // 4. State enum has all variants
    assert!(generated.contains("Empty,"));
    assert!(generated.contains("HasItems {"));
    assert!(generated.contains("CheckedOut {"));
    // 5. Transition method exists with correct signature
    assert!(generated.contains("pub fn checkout(&mut self"));
    // 6. Effect trait exists
    assert!(generated.contains("pub trait CartEffects"));
    assert!(generated.contains("fn compute_receipt(&self, items: &[Item]) -> String"));
    // 7. Effects are called with references
    assert!(generated.contains("effects.compute_receipt(&"));
}

#[test]
fn tuple_types_parse_and_codegen() {
    let source = r#"
type PairHolder {
    pair: (String, i64),
}
"#;

    let program = parse_program(source).expect("source should parse");
    let generated = RustCodegen::new().generate(&program);

    assert!(generated.contains("pub pair: (String, i64),"));
}

#[test]
fn test_implicit_ctx_rewrite() {
    // Implicit ctx: handler body uses ctx.field without declaring ctx as a parameter
    let source = r#"
type Config {
    name: String,
    count: i64,
}
machine Pipeline {
    state Waiting(config: Config)
    state Running(config: Config)
    state Done(name: String)

    transition start: Waiting -> Running
    transition finish: Running -> Done

    effect log_start(name: String) -> bool

    on start() {
        goto Running(ctx.config);
    }

    on finish() {
        perform log_start(ctx.config.name);
        goto Done(ctx.config.name);
    }
}
"#;
    let program = parse_program(source).expect("should parse");
    let generated = RustCodegen::new().generate(&program);

    // ctx.field must be rewritten even without explicit ctx param
    assert!(
        !generated.contains("ctx."),
        "implicit ctx references should be rewritten"
    );

    // Single-level: ctx.config → config
    assert!(
        generated.contains("config: config"),
        "ctx.config in goto should become config"
    );

    // Nested: ctx.config.name → config.name
    assert!(
        generated.contains("config.name"),
        "ctx.config.name should become config.name"
    );

    // No ctx parameter in method signatures
    assert!(
        !generated.contains("ctx:"),
        "no ctx param in method signatures"
    );
}

#[test]
fn test_effect_string_param_generates_str_ref() {
    let source = r#"
machine Notifier {
    state Idle
    state Done
    transition notify: Idle -> Done
    effect send_email(to: String, subject: String) -> bool
    async effect send_sms(number: String) -> bool
    effect log_count(count: i64) -> bool
    on notify() {
        perform send_email("a", "b");
        goto Done;
    }
}
"#;
    let program = parse_program(source).expect("should parse");
    let generated = RustCodegen::new().generate(&program);

    // String params in effect trait should generate &str, not &String
    assert!(
        generated.contains("to: &str"),
        "String param should be &str"
    );
    assert!(
        generated.contains("subject: &str"),
        "String param should be &str"
    );
    assert!(
        generated.contains("number: &str"),
        "async String param should be &str"
    );

    // Copy types should be passed by value, not reference
    assert!(
        generated.contains("count: i64"),
        "i64 param should be by value"
    );
    assert!(
        !generated.contains("count: &i64"),
        "i64 should not be by reference"
    );

    // No &String in effect trait signatures
    assert!(
        !generated.contains("&String"),
        "should not have &String in effect trait"
    );
}

#[test]
fn test_unused_state_fields_prefixed_with_underscore() {
    let source = r#"
type Config { name: String, retries: i64 }
machine Pipeline {
    state Running(config: Config, attempt: i64, tag: String)
    state Done(msg: String)
    transition finish: Running -> Done
    on finish(ctx: FinishCtx) {
        goto Done(ctx.config.name);
    }
}
"#;
    let program = parse_program(source).expect("should parse");
    let generated = RustCodegen::new().generate(&program);

    // config is used (ctx.config.name → config.name), so no underscore
    assert!(
        generated.contains("Running { config,"),
        "used field should not have underscore prefix: {generated}"
    );
    // attempt and tag are unused, so should be prefixed
    assert!(
        generated.contains("_attempt"),
        "unused field 'attempt' should have underscore prefix: {generated}"
    );
    assert!(
        generated.contains("_tag"),
        "unused field 'tag' should have underscore prefix: {generated}"
    );
}
