//! Integration tests for the CLI tool
//!
//! These tests verify that the `sb` command-line tool works correctly
//! by testing it against real project files.

use std::{
    fs,
    path::PathBuf,
    process::Command,
};

use tempfile::TempDir;

/// Helper to get the path to the compiled sb binary
fn sb_binary() -> PathBuf {
    let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    path.push("target");
    path.push("debug");
    path.push("sb");
    path
}

/// Helper to create a temporary test project
fn create_test_project() -> TempDir {
    let dir = TempDir::new().unwrap();

    // Create a valid character file
    fs::write(
        dir.path().join("test.sb"),
        r#"
character Martha {
    age: 34
    trust: 0.8
}

character David {
    age: 42
    health: 0.9
}
"#,
    )
    .unwrap();

    dir
}

/// Helper to create a project with errors
fn create_invalid_project() -> TempDir {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("errors.sb"),
        r#"
character Martha {
    age: 200
    trust: 1.5
}
"#,
    )
    .unwrap();

    dir
}

#[test]
fn test_validate_valid_project() {
    let project = create_test_project();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(project.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Validation should succeed for valid project. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Characters:    2"));
}

#[test]
fn test_validate_invalid_project() {
    let project = create_invalid_project();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(project.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        !output.status.success(),
        "Validation should fail for invalid project"
    );

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("Found 2 errors"));
    assert!(stderr.contains("Trait 'age' has value 200"));
    assert!(stderr.contains("Trait 'trust' has value 1.5"));
}

#[test]
fn test_validate_single_file() {
    let project = create_test_project();
    let file_path = project.path().join("test.sb");

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(&file_path)
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Validation should succeed for valid file"
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
}

#[test]
fn test_validate_nonexistent_path() {
    let output = Command::new(sb_binary())
        .arg("validate")
        .arg("/nonexistent/path/to/project")
        .output()
        .expect("Failed to execute sb validate");

    assert!(!output.status.success(), "Should fail for nonexistent path");

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("Path does not exist"));
}

#[test]
fn test_inspect_character() {
    let project = create_test_project();

    let output = Command::new(sb_binary())
        .arg("inspect")
        .arg("Martha")
        .arg("--path")
        .arg(project.path())
        .output()
        .expect("Failed to execute sb inspect");

    assert!(output.status.success(), "Inspect should succeed");

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("Character: Martha"));
    assert!(stdout.contains("age"));
    assert!(stdout.contains("34"));
}

#[test]
fn test_inspect_nonexistent_entity() {
    let project = create_test_project();

    let output = Command::new(sb_binary())
        .arg("inspect")
        .arg("NonExistent")
        .arg("--path")
        .arg(project.path())
        .output()
        .expect("Failed to execute sb inspect");

    assert!(output.status.success(), "Inspect runs even if not found");

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("not found"));
}

#[test]
fn test_validate_empty_project() {
    let dir = TempDir::new().unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(!output.status.success(), "Should fail for empty project");

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("No .sb files found"));
}

#[test]
fn test_validate_shows_multiple_errors() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("multi_error.sb"),
        r#"
character Alice {
    age: 200
    trust: 1.5
    bond: -0.2
}

character Bob {
    age: -10
    love: 3.0
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        !output.status.success(),
        "Should fail with validation errors"
    );

    let stderr = String::from_utf8_lossy(&output.stderr);

    // Should show all 5 errors (non-fail-fast)
    assert!(stderr.contains("Found 5 errors"));
    assert!(stderr.contains("age")); // age: 200
    assert!(stderr.contains("trust")); // trust: 1.5
    assert!(stderr.contains("bond")); // bond: -0.2
    assert!(stderr.contains("-10")); // age: -10
    assert!(stderr.contains("love")); // love: 3.0
}

#[test]
fn test_cross_file_name_resolution() {
    let dir = TempDir::new().unwrap();

    // Create multiple files with characters
    fs::write(
        dir.path().join("file1.sb"),
        r#"
character Martha {
    age: 34
    trust: 0.8
}
"#,
    )
    .unwrap();

    fs::write(
        dir.path().join("file2.sb"),
        r#"
character David {
    age: 42
    health: 0.9
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Should successfully load and validate multiple files"
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Characters:    2"));
}

#[test]
fn test_cross_file_duplicate_detection() {
    let dir = TempDir::new().unwrap();

    // Create two files with the same character name
    fs::write(
        dir.path().join("file1.sb"),
        r#"
character Martha {
    age: 34
}
"#,
    )
    .unwrap();

    fs::write(
        dir.path().join("file2.sb"),
        r#"
character Martha {
    age: 42
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        !output.status.success(),
        "Should fail with duplicate definition error"
    );

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("Duplicate definition of 'Martha'"));
}

// ===== Template Composition Tests =====

#[test]
fn test_template_composition_with_includes() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("templates.sb"),
        r#"
// Base template
template Being {
    alive: true
}

// Template that includes Being
template Human {
    include Being
    kind: "human"
}

// Character with template composition
character Martha from Human {
    firstName: "Martha"
    age: 34
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Template composition with includes should succeed"
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful!"));
    assert!(stdout.contains("Characters:    1"));
}

#[test]
fn test_template_composition_multiple_templates() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("templates.sb"),
        r#"
template Physical {
    height: 0
    weight: 0
}

template Mental {
    iq: 0
}

character David from Physical, Mental {
    height: 180
    weight: 75
    iq: 120
    firstName: "David"
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Multiple template inheritance should succeed"
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful!"));
}

#[test]
fn test_strict_template_validation_success() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("strict.sb"),
        r#"
template Person strict {
    age: 18..100
    firstName: ""
}

character Martha from Person {
    age: 34
    firstName: "Martha"
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Strict template with concrete values should succeed"
    );
}

#[test]
fn test_strict_template_validation_failure() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("strict_fail.sb"),
        r#"
template Person strict {
    age: 18..100
}

character Martha from Person {
    firstName: "Martha"
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        !output.status.success(),
        "Strict template with range value should fail"
    );

    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stderr.contains("strict template"));
    assert!(stderr.contains("range value"));
}

#[test]
fn test_template_chained_includes() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("chained.sb"),
        r#"
template Being {
    alive: true
}

template Human {
    include Being
    kind: "human"
}

template Person strict {
    include Human
    age: 18..100
}

character Martha from Person {
    age: 34
    firstName: "Martha"
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Chained template includes should succeed"
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful!"));
}

#[test]
fn test_reserved_keyword_field_name_fails_at_parse() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("reserved.sb"),
        r#"
character Martha {
    species: "human"
    age: 34
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        !output.status.success(),
        "Field with reserved keyword name should fail at parse time"
    );

    let stderr = String::from_utf8_lossy(&output.stderr);
    // Parser catches this as UnrecognizedToken before validation
    assert!(stderr.contains("Parse error") || stderr.contains("UnrecognizedToken"));
}

// ===== Prose Block Tests =====

#[test]
fn test_prose_block_in_institution() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("institution.sb"),
        r#"
institution Bakery {
    type: "business"

    ---description
    A cozy neighborhood bakery that has been serving
    the community for over 30 years. Known for its
    fresh bread and friendly atmosphere.
    ---

    established: 1990
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Institution with prose block should validate successfully. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Institutions:  1"));
}

#[test]
fn test_multiple_prose_blocks_in_same_declaration() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("multi_prose.sb"),
        r#"
character Martha {
    age: 34

    ---backstory
    Martha grew up in a small town where she learned
    the art of baking from her grandmother. Her passion
    for creating delicious pastries has been lifelong.
    ---

    trust: 0.8

    ---personality
    Martha is kind-hearted and patient, always willing
    to help her neighbors. She has a warm smile that
    makes everyone feel welcome in her bakery.
    ---

    ---goals
    She dreams of expanding her bakery and teaching
    baking classes to the next generation of bakers
    in the community.
    ---
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Character with multiple prose blocks should validate successfully. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Characters:    1"));
}

#[test]
fn test_prose_blocks_in_behavior_tree() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("behavior.sb"),
        r#"
behavior WakeUpRoutine {
    ---description
    A simple morning routine that characters follow
    when they wake up each day.
    ---

    then {
        WakeUp
        GetDressed
        EatBreakfast
    }
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Behavior tree with prose block should validate successfully. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Behaviors:     1"));
}

#[test]
fn test_prose_blocks_in_life_arc() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("life_arc.sb"),
        r#"
life_arc BakerCareer {
    ---description
    The journey of becoming a master baker, from
    apprentice to owning one's own bakery.
    ---

    state Apprentice {
        ---details
        Learning the basics of bread-making and pastry
        under the guidance of an experienced baker.
        ---

        on experience > 5 -> Journeyman
    }

    state Journeyman {
        ---details
        Working independently and developing signature
        recipes while saving to open a bakery.
        ---

        on savings > 50000 -> MasterBaker
    }

    state MasterBaker {
        ---details
        Running a successful bakery and mentoring the
        next generation of bakers.
        ---
    }
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Life arc with prose blocks should validate successfully. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Life Arcs:     1"));
}

#[test]
fn test_prose_blocks_in_schedule() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("schedule.sb"),
        r#"
schedule DailyRoutine {
    ---description
    A typical weekday schedule for bakery workers
    who start their day before dawn.
    ---

    04:00 -> 06:00: PrepareDough { }
    06:00 -> 08:00: BakeBread { }
    08:00 -> 12:00: ServCustomers { }
    12:00 -> 13:00: LunchBreak { }
    13:00 -> 17:00: ServCustomers { }
    17:00 -> 18:00: Cleanup { }
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Schedule with prose block should validate successfully. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Schedules:     1"));
}

#[test]
fn test_prose_blocks_with_special_characters() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("special.sb"),
        r#"
character Alice {
    age: 7

    ---backstory
    Alice was a curious young girl who often wondered about
    the world around her. One day, she saw a White Rabbit
    muttering "Oh dear! Oh dear! I shall be too late!" and
    followed it down a rabbit-hole.

    She fell down, down, down--would the fall never come to
    an end? "I wonder how many miles I've fallen by this time?"
    she said aloud.
    ---

    curiosity: 0.95
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Prose block with quotes, dashes, and punctuation should validate. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
}

#[test]
fn test_prose_blocks_in_relationship() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("relationship.sb"),
        r#"
character Martha {
    age: 34
}

character David {
    age: 42
}

relationship Spousal {
    Martha {}
    David {}

    ---bond_description
    Martha and David have been married for 8 years.
    They share a deep connection built on mutual
    respect, trust, and shared dreams for the future.
    ---

    bond: 0.9
    coordination: cohabiting
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Relationship with prose block should validate successfully. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Characters:    2"));
    assert!(stdout.contains("Relationships: 1"));
}

// ===== Species Tests =====

#[test]
fn test_species_with_identity_operator() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("species.sb"),
        r#"
species Human {
    lifespan: 70..90
    intelligence: "high"
}

character Alice: Human {
    age: 7
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Character with species should validate successfully. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Characters:    1"));
}

#[test]
fn test_species_composition_with_includes() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("hybrid_species.sb"),
        r#"
species Horse {
    hoofed: true
    mane_style: "long"
}

species Donkey {
    hoofed: true
    ear_size: "large"
}

species Mule {
    include Horse
    include Donkey
    sterile: true
}

character MyMule: Mule {
    age: 3
    name: "Betsy"
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Hybrid species with composition should validate successfully. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Characters:    1"));
}

#[test]
fn test_character_without_species() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("no_species.sb"),
        r#"
character TheNarrator {
    abstract_entity: true
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Character without species should validate (species is optional). Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
}

#[test]
fn test_species_with_templates() {
    let dir = TempDir::new().unwrap();

    fs::write(
        dir.path().join("species_and_templates.sb"),
        r#"
species Human {
    lifespan: 70..90
}

template Hero {
    courage: 0.9
    strength: 0.8
}

character Alice: Human from Hero {
    age: 7
    name: "Alice"
}
"#,
    )
    .unwrap();

    let output = Command::new(sb_binary())
        .arg("validate")
        .arg(dir.path())
        .output()
        .expect("Failed to execute sb validate");

    assert!(
        output.status.success(),
        "Character with both species and templates should validate. Stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("✓ Validation successful"));
    assert!(stdout.contains("Characters:    1"));
}
