//! Test Generation for Purified Bash Scripts
//!
//! Generates comprehensive test suites for purified bash scripts to ensure:
//! - Determinism: Same inputs always produce same outputs
//! - Idempotency: Safe to re-run multiple times
//! - POSIX Compliance: Generated tests pass shellcheck
//!
//! EXTREME TDD: This module implements Phase 2 (GREEN) to make CLI tests pass.

use std::path::Path;

/// Test generation options
#[derive(Debug, Clone)]
pub struct TestGeneratorOptions {
    /// Generate property-based tests (100+ cases)
    pub property_tests: bool,

    /// Number of property test cases to generate
    pub property_test_count: usize,
}

impl Default for TestGeneratorOptions {
    fn default() -> Self {
        Self {
            property_tests: false,
            property_test_count: 100,
        }
    }
}

/// Test generator for purified bash scripts
pub struct TestGenerator {
    options: TestGeneratorOptions,
}

impl TestGenerator {
    /// Create a new test generator with given options
    pub fn new(options: TestGeneratorOptions) -> Self {
        Self { options }
    }

    /// Extract script name from path
    #[allow(clippy::expect_used)]
    fn get_script_name(script_path: &Path) -> &str {
        script_path
            .file_name()
            .expect("Script path should have a file name")
            .to_str()
            .expect("File name should be valid UTF-8")
    }

    /// Generate test suite for a purified script
    ///
    /// # Arguments
    /// * `script_path` - Path to the purified script file
    /// * `_purified_content` - Content of the purified script (for future analysis)
    ///
    /// # Returns
    /// Generated test suite as a String
    pub fn generate_tests(&self, script_path: &Path, _purified_content: &str) -> String {
        let mut test_suite = String::new();

        // Shebang
        test_suite.push_str("#!/bin/sh\n");
        test_suite.push_str("# Test Suite for ");
        test_suite.push_str(Self::get_script_name(script_path));
        test_suite.push('\n');
        test_suite.push_str("# Generated by bashrs purify --with-tests\n\n");
        test_suite.push_str("set -e  # Exit on first failure\n\n");

        // Test 1: Determinism
        test_suite.push_str(&self.generate_determinism_test(script_path));
        test_suite.push('\n');

        // Test 2: Idempotency
        test_suite.push_str(&self.generate_idempotency_test(script_path));
        test_suite.push('\n');

        // Test 3: POSIX Compliance
        test_suite.push_str(&self.generate_posix_compliance_test(script_path));
        test_suite.push('\n');

        // Test 4: Property-based tests (if enabled)
        if self.options.property_tests {
            test_suite.push_str(&self.generate_property_determinism_test(script_path));
            test_suite.push('\n');
        }

        // Test Runner
        test_suite.push_str(&self.generate_test_runner());

        test_suite
    }

    /// Generate determinism test
    ///
    /// Tests that running the script twice with same inputs produces same outputs
    fn generate_determinism_test(&self, script_path: &Path) -> String {
        let script_name = Self::get_script_name(script_path);

        format!(
            r#"# Test 1: Determinism - Same inputs produce same outputs
test_determinism() {{
    echo "Testing: Determinism..."

    # Run script twice with same inputs
    output1=$(./{} 2>&1 || true)
    output2=$(./{} 2>&1 || true)

    if [ "$output1" = "$output2" ]; then
        echo "  ✅ PASS: Outputs are identical"
        return 0
    else
        echo "  ❌ FAIL: Outputs differ"
        echo "    Run 1: $output1"
        echo "    Run 2: $output2"
        return 1
    fi
}}
"#,
            script_name, script_name
        )
    }

    /// Generate idempotency test
    ///
    /// Tests that the script can be run multiple times safely
    fn generate_idempotency_test(&self, script_path: &Path) -> String {
        let script_name = Self::get_script_name(script_path);

        format!(
            r#"# Test 2: Idempotency - Safe to re-run
test_idempotency() {{
    echo "Testing: Idempotency..."

    # First run
    ./{} >/dev/null 2>&1 || {{
        echo "  ⚠️  SKIP: Script requires specific setup"
        return 0
    }}

    # Second run (should not fail)
    ./{} >/dev/null 2>&1 || {{
        echo "  ❌ FAIL: Second run failed (not idempotent)"
        return 1
    }}

    echo "  ✅ PASS: Script is idempotent"
    return 0
}}
"#,
            script_name, script_name
        )
    }

    /// Generate POSIX compliance test
    ///
    /// Tests that the purified script passes shellcheck
    fn generate_posix_compliance_test(&self, script_path: &Path) -> String {
        let script_name = Self::get_script_name(script_path);

        format!(
            r#"# Test 3: POSIX Compliance
test_posix_compliance() {{
    echo "Testing: POSIX Compliance..."

    if command -v shellcheck >/dev/null 2>&1; then
        shellcheck -s sh ./{} || {{
            echo "  ❌ FAIL: shellcheck found POSIX violations"
            return 1
        }}
        echo "  ✅ PASS: POSIX compliant (shellcheck)"
    else
        echo "  ⚠️  SKIP: shellcheck not installed"
    fi
    return 0
}}
"#,
            script_name
        )
    }

    /// Generate property-based determinism test
    ///
    /// Tests determinism with multiple generated inputs
    fn generate_property_determinism_test(&self, script_path: &Path) -> String {
        let script_name = Self::get_script_name(script_path);
        let test_count = self.options.property_test_count;

        format!(
            r#"# Property Test 4: Determinism holds for all inputs ({} cases)
test_property_determinism() {{
    echo "Testing: Determinism Property ({} cases)..."

    passed=0
    failed=0

    for i in $(seq 1 {}); do
        # Generate test input
        test_input="test_case_$i"

        # Run script twice with same input
        output1=$(./{} "$test_input" 2>&1 || true)
        output2=$(./{} "$test_input" 2>&1 || true)

        if [ "$output1" = "$output2" ]; then
            passed=$((passed + 1))
        else
            failed=$((failed + 1))
            echo "    ❌ Case $i failed (input=$test_input)"
        fi
    done

    if [ $failed -eq 0 ]; then
        echo "  ✅ PASS: Determinism property ($passed/{} cases)"
        return 0
    else
        echo "  ❌ FAIL: $failed/{} cases failed"
        return 1
    fi
}}
"#,
            test_count, test_count, test_count, script_name, script_name, test_count, test_count
        )
    }

    /// Generate test runner
    ///
    /// Runs all tests and reports results
    fn generate_test_runner(&self) -> String {
        let mut runner = String::new();

        runner.push_str("# Test Runner\n");
        runner.push_str("echo \"================================================\"\n");
        runner.push_str("echo \"bashrs purify --with-tests\"\n");
        runner.push_str("echo \"Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)\"\n");
        runner.push_str("echo \"================================================\"\n");
        runner.push_str("echo \"\"\n\n");

        runner.push_str("# Run all tests\n");
        runner.push_str("failed_tests=\"\"\n\n");

        runner.push_str("test_determinism || failed_tests=\"$failed_tests test_determinism\"\n");
        runner.push_str("test_idempotency || failed_tests=\"$failed_tests test_idempotency\"\n");
        runner.push_str(
            "test_posix_compliance || failed_tests=\"$failed_tests test_posix_compliance\"\n",
        );

        if self.options.property_tests {
            runner.push_str("test_property_determinism || failed_tests=\"$failed_tests test_property_determinism\"\n");
        }

        runner.push_str("\necho \"\"\n");
        runner.push_str("echo \"================================================\"\n\n");

        runner.push_str("if [ -z \"$failed_tests\" ]; then\n");
        runner.push_str("    echo \"✅ All tests passed!\"\n");
        runner.push_str("    exit 0\n");
        runner.push_str("else\n");
        runner.push_str("    echo \"❌ Failed tests:$failed_tests\"\n");
        runner.push_str("    exit 1\n");
        runner.push_str("fi\n");

        runner
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    #[test]
    fn test_generator_creates_posix_shebang() {
        let options = TestGeneratorOptions::default();
        let generator = TestGenerator::new(options);
        let script_path = PathBuf::from("test_script.sh");

        let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

        assert!(tests.starts_with("#!/bin/sh"), "Should have POSIX shebang");
    }

    #[test]
    fn test_generator_includes_determinism_test() {
        let options = TestGeneratorOptions::default();
        let generator = TestGenerator::new(options);
        let script_path = PathBuf::from("test_script.sh");

        let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

        assert!(
            tests.contains("test_determinism"),
            "Should include determinism test"
        );
        assert!(tests.contains("output1"), "Should compare multiple outputs");
        assert!(tests.contains("output2"), "Should compare multiple outputs");
    }

    #[test]
    fn test_generator_includes_idempotency_test() {
        let options = TestGeneratorOptions::default();
        let generator = TestGenerator::new(options);
        let script_path = PathBuf::from("test_script.sh");

        let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

        assert!(
            tests.contains("test_idempotency"),
            "Should include idempotency test"
        );
    }

    #[test]
    fn test_generator_includes_posix_test() {
        let options = TestGeneratorOptions::default();
        let generator = TestGenerator::new(options);
        let script_path = PathBuf::from("test_script.sh");

        let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

        assert!(
            tests.contains("test_posix_compliance"),
            "Should include POSIX test"
        );
        assert!(tests.contains("shellcheck"), "Should use shellcheck");
    }

    #[test]
    fn test_generator_property_tests_optional() {
        // Without property tests
        let options = TestGeneratorOptions {
            property_tests: false,
            property_test_count: 100,
        };
        let generator = TestGenerator::new(options);
        let script_path = PathBuf::from("test_script.sh");

        let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

        assert!(
            !tests.contains("test_property_determinism"),
            "Should not include property tests by default"
        );
    }

    #[test]
    fn test_generator_property_tests_enabled() {
        // With property tests
        let options = TestGeneratorOptions {
            property_tests: true,
            property_test_count: 50,
        };
        let generator = TestGenerator::new(options);
        let script_path = PathBuf::from("test_script.sh");

        let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

        assert!(
            tests.contains("test_property_determinism"),
            "Should include property tests when enabled"
        );
        assert!(
            tests.contains("50 cases"),
            "Should include configured number of cases"
        );
    }

    #[test]
    fn test_generator_creates_valid_sh_syntax() {
        let options = TestGeneratorOptions::default();
        let generator = TestGenerator::new(options);
        let script_path = PathBuf::from("test_script.sh");

        let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

        // Check for valid sh constructs
        assert!(tests.contains("if ["), "Should use POSIX test syntax");
        assert!(
            tests.contains("return 0"),
            "Should have proper return codes"
        );
        assert!(tests.contains("exit 0"), "Should have proper exit codes");
    }

    #[test]
    fn test_generator_includes_test_runner() {
        let options = TestGeneratorOptions::default();
        let generator = TestGenerator::new(options);
        let script_path = PathBuf::from("test_script.sh");

        let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

        assert!(tests.contains("Test Runner"), "Should include test runner");
        assert!(tests.contains("All tests passed"), "Should report success");
        assert!(tests.contains("Failed tests"), "Should report failures");
    }
}

#[cfg(test)]
mod property_tests {
    use super::*;
    use proptest::prelude::*;
    use std::path::PathBuf;

    proptest! {
        /// Property: Generated tests always have POSIX shebang
        #[test]
        fn prop_generated_tests_have_posix_shebang(
            script_name in "[a-z][a-z0-9_]{0,20}\\.sh"
        ) {
            let options = TestGeneratorOptions::default();
            let generator = TestGenerator::new(options);
            let script_path = PathBuf::from(&script_name);

            let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

            prop_assert!(tests.starts_with("#!/bin/sh"));
        }

        /// Property: Generated tests always include script name in header
        #[test]
        fn prop_generated_tests_include_script_name(
            script_name in "[a-z][a-z0-9_]{0,20}\\.sh"
        ) {
            let options = TestGeneratorOptions::default();
            let generator = TestGenerator::new(options);
            let script_path = PathBuf::from(&script_name);

            let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

            prop_assert!(tests.contains(&script_name));
        }

        /// Property: Generated tests always include all 3 core test functions
        #[test]
        fn prop_generated_tests_include_all_core_functions(
            script_name in "[a-z][a-z0-9_]{0,20}\\.sh"
        ) {
            let options = TestGeneratorOptions::default();
            let generator = TestGenerator::new(options);
            let script_path = PathBuf::from(&script_name);

            let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

            prop_assert!(tests.contains("test_determinism"));
            prop_assert!(tests.contains("test_idempotency"));
            prop_assert!(tests.contains("test_posix_compliance"));
        }

        /// Property: Generated tests always include test runner
        #[test]
        fn prop_generated_tests_include_runner(
            script_name in "[a-z][a-z0-9_]{0,20}\\.sh"
        ) {
            let options = TestGeneratorOptions::default();
            let generator = TestGenerator::new(options);
            let script_path = PathBuf::from(&script_name);

            let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

            prop_assert!(tests.contains("Test Runner"));
            prop_assert!(tests.contains("All tests passed"));
            prop_assert!(tests.contains("Failed tests"));
        }

        /// Property: Property tests are only included when enabled
        #[test]
        fn prop_property_tests_conditional(
            script_name in "[a-z][a-z0-9_]{0,20}\\.sh",
            enable_property in prop::bool::ANY
        ) {
            let options = TestGeneratorOptions {
                property_tests: enable_property,
                property_test_count: 100,
            };
            let generator = TestGenerator::new(options);
            let script_path = PathBuf::from(&script_name);

            let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

            if enable_property {
                prop_assert!(tests.contains("test_property_determinism"));
            } else {
                prop_assert!(!tests.contains("test_property_determinism"));
            }
        }

        /// Property: Property test count is respected
        #[test]
        fn prop_property_test_count_respected(
            script_name in "[a-z][a-z0-9_]{0,20}\\.sh",
            test_count in 10usize..200usize
        ) {
            let options = TestGeneratorOptions {
                property_tests: true,
                property_test_count: test_count,
            };
            let generator = TestGenerator::new(options);
            let script_path = PathBuf::from(&script_name);

            let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

            // Check that the test count appears in the generated tests
            let expected = format!("{} cases", test_count);
            prop_assert!(tests.contains(&expected));
        }

        /// Property: Generated tests always use POSIX-compliant syntax
        #[test]
        fn prop_generated_tests_use_posix_syntax(
            script_name in "[a-z][a-z0-9_]{0,20}\\.sh"
        ) {
            let options = TestGeneratorOptions::default();
            let generator = TestGenerator::new(options);
            let script_path = PathBuf::from(&script_name);

            let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

            // Check for POSIX-compliant constructs
            prop_assert!(tests.contains("if ["));  // POSIX test syntax
            prop_assert!(tests.contains("return 0")); // Standard return codes
            prop_assert!(tests.contains("exit 0")); // Standard exit codes
            prop_assert!(!tests.contains("[[")); // No bash double-bracket tests
            // Note: tests contains "==" in comment lines ("=========...") so we don't check for that
        }

        /// Property: Script name in test path matches input
        #[test]
        fn prop_script_name_consistency(
            script_name in "[a-z][a-z0-9_]{0,20}\\.sh"
        ) {
            let options = TestGeneratorOptions::default();
            let generator = TestGenerator::new(options);
            let script_path = PathBuf::from(&script_name);

            let tests = generator.generate_tests(&script_path, "#!/bin/sh\necho test");

            // Script name should appear in the generated tests
            prop_assert!(tests.contains(&script_name.replace(".sh", "")));
        }
    }
}
