//! Popperian Falsification QA Generator
//!
//! Generates 100-point falsification test suites from specifications.
//! Following Popper's principle: "The wrong view of science betrays itself
//! in the craving to be right."
//!
//! # Categories (100 points total)
//!
//! - Boundary Conditions (20 points): BC-001..005
//! - Invariant Violations (20 points): INV-001..004
//! - Numerical Stability (20 points): NUM-001..003
//! - Concurrency (15 points): CONC-001..003
//! - Resource Exhaustion (15 points): RES-001..003
//! - Cross-Implementation Parity (10 points): PAR-001..002
//!
//! # Toyota Production System Principles
//!
//! - **Jidoka**: Stop-on-error in test execution
//! - **Genchi Genbutsu**: Evidence-based verification
//! - **Kaizen**: Continuous improvement via falsification rate tracking

pub mod categories;
pub mod generator;
pub mod parser;
pub mod report;
pub mod template;

pub use generator::{FalsifyGenerator, GeneratedTest, TargetLanguage};
pub use parser::SpecParser;
#[allow(unused_imports)]
pub use parser::{ParsedRequirement, ParsedSpec};
#[allow(unused_imports)]
pub use report::{FalsificationReport, FalsificationSummary, TestOutcome};
pub use template::FalsificationTemplate;
#[allow(unused_imports)]
pub use template::{CategoryTemplate, TestTemplate};

/// Main Falsification Engine
///
/// Coordinates test generation from specifications.
#[derive(Debug)]
pub struct FalsifyEngine {
    /// 100-point template
    template: FalsificationTemplate,
    /// Specification parser
    parser: SpecParser,
    /// Test generator
    generator: FalsifyGenerator,
}

impl FalsifyEngine {
    /// Create a new falsification engine with default template
    pub fn new() -> Self {
        Self {
            template: FalsificationTemplate::default(),
            parser: SpecParser::new(),
            generator: FalsifyGenerator::new(),
        }
    }

    /// Generate falsification suite from a specification file
    pub fn generate_from_spec(
        &self,
        spec_path: &std::path::Path,
        language: TargetLanguage,
    ) -> anyhow::Result<GeneratedSuite> {
        // Parse spec
        let spec = self.parser.parse_file(spec_path)?;

        // Generate tests
        let tests = self.generator.generate(&spec, &self.template, language)?;

        Ok(GeneratedSuite {
            spec_name: spec.name.clone(),
            language,
            tests,
            total_points: self.template.total_points(),
        })
    }

    /// Generate with custom point allocation
    pub fn generate_with_points(
        &self,
        spec_path: &std::path::Path,
        language: TargetLanguage,
        target_points: u32,
    ) -> anyhow::Result<GeneratedSuite> {
        let spec = self.parser.parse_file(spec_path)?;
        let template = self.template.scale_to_points(target_points);
        let tests = self.generator.generate(&spec, &template, language)?;

        Ok(GeneratedSuite {
            spec_name: spec.name.clone(),
            language,
            tests,
            total_points: target_points,
        })
    }

    /// Get the template for inspection
    pub fn template(&self) -> &FalsificationTemplate {
        &self.template
    }
}

impl Default for FalsifyEngine {
    fn default() -> Self {
        Self::new()
    }
}

/// A generated test suite
#[derive(Debug)]
pub struct GeneratedSuite {
    /// Name from specification
    pub spec_name: String,
    /// Target language
    pub language: TargetLanguage,
    /// Generated tests
    pub tests: Vec<GeneratedTest>,
    /// Total points
    pub total_points: u32,
}

impl GeneratedSuite {
    /// Format as code string
    pub fn to_code(&self) -> String {
        self.generator_code()
    }

    /// Generate code for target language
    fn generator_code(&self) -> String {
        match self.language {
            TargetLanguage::Rust => self.to_rust(),
            TargetLanguage::Python => self.to_python(),
        }
    }

    /// Generate Rust test file
    fn to_rust(&self) -> String {
        let mut out = String::new();
        out.push_str(&format!("//! Falsification Suite: {}\n", self.spec_name));
        out.push_str(&format!("//! Total Points: {}\n", self.total_points));
        out.push_str("//! Generated by batuta oracle falsify\n\n");
        out.push_str("#![cfg(test)]\n\n");
        out.push_str("use proptest::prelude::*;\n\n");

        // Group by category
        let mut current_category = String::new();
        for test in &self.tests {
            if test.category != current_category {
                current_category = test.category.clone();
                out.push_str(&format!(
                    "\n// {:=<60}\n",
                    format!(" {} ", current_category.to_uppercase())
                ));
            }

            out.push_str(&format!("\n/// {}: {}\n", test.id, test.name));
            out.push_str(&format!("/// Points: {}\n", test.points));
            out.push_str(&test.code);
            out.push('\n');
        }

        out
    }

    /// Generate Python test file
    fn to_python(&self) -> String {
        let mut out = String::new();
        out.push_str(&format!(
            "\"\"\"Falsification Suite: {}\n\n",
            self.spec_name
        ));
        out.push_str(&format!("Total Points: {}\n", self.total_points));
        out.push_str("Generated by batuta oracle falsify\n\"\"\"\n\n");
        out.push_str("import pytest\n");
        out.push_str("from hypothesis import given, strategies as st\n\n");

        // Group by category
        let mut current_category = String::new();
        for test in &self.tests {
            if test.category != current_category {
                current_category = test.category.clone();
                out.push_str(&format!(
                    "\n# {:=<60}\n",
                    format!(" {} ", current_category.to_uppercase())
                ));
            }

            out.push_str(&format!(
                "\ndef test_{}():\n",
                test.id.to_lowercase().replace('-', "_")
            ));
            out.push_str(&format!("    \"\"\"{}: {}\n", test.id, test.name));
            out.push_str(&format!("    Points: {}\n    \"\"\"\n", test.points));
            out.push_str(&test.code);
            out.push('\n');
        }

        out
    }

    /// Get tests by category
    pub fn tests_by_category(&self) -> std::collections::HashMap<String, Vec<&GeneratedTest>> {
        let mut map = std::collections::HashMap::new();
        for test in &self.tests {
            map.entry(test.category.clone())
                .or_insert_with(Vec::new)
                .push(test);
        }
        map
    }

    /// Get summary statistics
    pub fn summary(&self) -> SuiteSummary {
        let mut points_by_category = std::collections::HashMap::new();
        for test in &self.tests {
            *points_by_category
                .entry(test.category.clone())
                .or_insert(0u32) += test.points;
        }

        SuiteSummary {
            spec_name: self.spec_name.clone(),
            total_tests: self.tests.len(),
            total_points: self.total_points,
            points_by_category,
        }
    }
}

/// Summary of a generated suite
#[derive(Debug)]
pub struct SuiteSummary {
    pub spec_name: String,
    pub total_tests: usize,
    pub total_points: u32,
    pub points_by_category: std::collections::HashMap<String, u32>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use template::TestSeverity;

    #[test]
    fn test_falsify_engine_creation() {
        let engine = FalsifyEngine::new();
        assert_eq!(engine.template.total_points(), 100);
    }

    #[test]
    fn test_template_categories() {
        let engine = FalsifyEngine::new();
        let template = engine.template();
        assert!(!template.categories.is_empty());
    }

    #[test]
    fn test_falsify_engine_default() {
        let engine = FalsifyEngine::default();
        assert_eq!(engine.template.total_points(), 100);
    }

    #[test]
    fn test_generated_suite_to_code_rust() {
        let suite = GeneratedSuite {
            spec_name: "test-spec".to_string(),
            language: TargetLanguage::Rust,
            tests: vec![GeneratedTest {
                id: "BC-001".to_string(),
                name: "Boundary test".to_string(),
                category: "boundary".to_string(),
                points: 4,
                severity: TestSeverity::High,
                code: "#[test]\nfn test_boundary() {}".to_string(),
            }],
            total_points: 100,
        };
        let code = suite.to_code();
        assert!(code.contains("test-spec"));
        assert!(code.contains("BC-001"));
        assert!(code.contains("proptest"));
    }

    #[test]
    fn test_generated_suite_to_code_python() {
        let suite = GeneratedSuite {
            spec_name: "test-spec".to_string(),
            language: TargetLanguage::Python,
            tests: vec![GeneratedTest {
                id: "BC-001".to_string(),
                name: "Boundary test".to_string(),
                category: "boundary".to_string(),
                points: 4,
                severity: TestSeverity::High,
                code: "    pass".to_string(),
            }],
            total_points: 100,
        };
        let code = suite.to_code();
        assert!(code.contains("test-spec"));
        assert!(code.contains("pytest"));
        assert!(code.contains("hypothesis"));
    }

    #[test]
    fn test_generated_suite_tests_by_category() {
        let suite = GeneratedSuite {
            spec_name: "test".to_string(),
            language: TargetLanguage::Rust,
            tests: vec![
                GeneratedTest {
                    id: "BC-001".to_string(),
                    name: "Test 1".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::High,
                    code: String::new(),
                },
                GeneratedTest {
                    id: "INV-001".to_string(),
                    name: "Test 2".to_string(),
                    category: "invariant".to_string(),
                    points: 5,
                    severity: TestSeverity::Critical,
                    code: String::new(),
                },
                GeneratedTest {
                    id: "BC-002".to_string(),
                    name: "Test 3".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::Medium,
                    code: String::new(),
                },
            ],
            total_points: 13,
        };
        let by_cat = suite.tests_by_category();
        assert_eq!(by_cat.len(), 2);
        assert_eq!(by_cat.get("boundary").expect("key not found").len(), 2);
        assert_eq!(by_cat.get("invariant").expect("key not found").len(), 1);
    }

    #[test]
    fn test_generated_suite_summary() {
        let suite = GeneratedSuite {
            spec_name: "my-spec".to_string(),
            language: TargetLanguage::Rust,
            tests: vec![
                GeneratedTest {
                    id: "BC-001".to_string(),
                    name: "Test 1".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::High,
                    code: String::new(),
                },
                GeneratedTest {
                    id: "BC-002".to_string(),
                    name: "Test 2".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::High,
                    code: String::new(),
                },
            ],
            total_points: 8,
        };
        let summary = suite.summary();
        assert_eq!(summary.spec_name, "my-spec");
        assert_eq!(summary.total_tests, 2);
        assert_eq!(summary.total_points, 8);
        assert_eq!(*summary.points_by_category.get("boundary").expect("key not found"), 8);
    }

    #[test]
    fn test_suite_summary_fields() {
        let summary = SuiteSummary {
            spec_name: "test".to_string(),
            total_tests: 10,
            total_points: 100,
            points_by_category: std::collections::HashMap::new(),
        };
        assert_eq!(summary.spec_name, "test");
        assert_eq!(summary.total_tests, 10);
        assert_eq!(summary.total_points, 100);
    }

    #[test]
    fn test_generated_suite_rust_code_format() {
        let suite = GeneratedSuite {
            spec_name: "spec".to_string(),
            language: TargetLanguage::Rust,
            tests: vec![
                GeneratedTest {
                    id: "BC-001".to_string(),
                    name: "First".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::High,
                    code: "// code".to_string(),
                },
                GeneratedTest {
                    id: "INV-001".to_string(),
                    name: "Second".to_string(),
                    category: "invariant".to_string(),
                    points: 5,
                    severity: TestSeverity::Critical,
                    code: "// more".to_string(),
                },
            ],
            total_points: 9,
        };
        let code = suite.to_code();
        // Should have category headers
        assert!(code.contains("BOUNDARY"));
        assert!(code.contains("INVARIANT"));
        // Should have cfg test
        assert!(code.contains("#![cfg(test)]"));
    }

    #[test]
    fn test_generated_suite_python_code_format() {
        let suite = GeneratedSuite {
            spec_name: "spec".to_string(),
            language: TargetLanguage::Python,
            tests: vec![GeneratedTest {
                id: "BC-001".to_string(),
                name: "Test".to_string(),
                category: "boundary".to_string(),
                points: 4,
                severity: TestSeverity::High,
                code: "    assert True".to_string(),
            }],
            total_points: 4,
        };
        let code = suite.to_code();
        // Should have proper Python function name
        assert!(code.contains("def test_bc_001"));
        // Should have docstring
        assert!(code.contains("BC-001: Test"));
    }

    // =====================================================================
    // Coverage: generate_from_spec and generate_with_points
    // =====================================================================

    #[test]
    fn test_generate_from_spec_with_file() {
        let dir = tempfile::TempDir::new().expect("tempdir creation failed");
        let spec_file = dir.path().join("test-spec.md");
        std::fs::write(
            &spec_file,
            r#"# My Test Spec
module: my_module

## Requirements
- MUST handle empty input gracefully
- SHOULD return error on invalid data
- The function MUST NOT panic on any input

## Functions
fn process_data(input: &[u8]) -> Result<Vec<u8>, Error>

## Types
struct DataProcessor { buffer: Vec<u8> }
"#,
        )
        .expect("unexpected failure");

        let engine = FalsifyEngine::new();
        let suite = engine
            .generate_from_spec(&spec_file, TargetLanguage::Rust)
            .expect("unexpected failure");

        assert_eq!(suite.spec_name, "test-spec");
        assert_eq!(suite.language, TargetLanguage::Rust);
        assert!(!suite.tests.is_empty());
        assert_eq!(suite.total_points, 100);

        // Verify generated code works
        let code = suite.to_code();
        assert!(code.contains("test-spec"));
        assert!(code.contains("my_module"));
    }

    #[test]
    fn test_generate_from_spec_python() {
        let dir = tempfile::TempDir::new().expect("tempdir creation failed");
        let spec_file = dir.path().join("py-spec.md");
        std::fs::write(
            &spec_file,
            "module: test_mod\n- MUST handle empty input\n- SHOULD validate",
        )
        .expect("unexpected failure");

        let engine = FalsifyEngine::new();
        let suite = engine
            .generate_from_spec(&spec_file, TargetLanguage::Python)
            .expect("unexpected failure");

        assert_eq!(suite.language, TargetLanguage::Python);
        let code = suite.to_code();
        assert!(code.contains("pytest"));
        assert!(code.contains("hypothesis"));
    }

    #[test]
    fn test_generate_from_spec_nonexistent_file() {
        let engine = FalsifyEngine::new();
        let result = engine.generate_from_spec(
            std::path::Path::new("/nonexistent/file.md"),
            TargetLanguage::Rust,
        );
        assert!(result.is_err());
    }

    #[test]
    fn test_generate_with_points() {
        let dir = tempfile::TempDir::new().expect("tempdir creation failed");
        let spec_file = dir.path().join("points-spec.md");
        std::fs::write(
            &spec_file,
            "module: scaled_module\n- MUST work correctly\n- SHOULD be fast",
        )
        .expect("unexpected failure");

        let engine = FalsifyEngine::new();
        let suite = engine
            .generate_with_points(&spec_file, TargetLanguage::Rust, 50)
            .expect("unexpected failure");

        assert_eq!(suite.spec_name, "points-spec");
        assert_eq!(suite.total_points, 50);
        assert!(!suite.tests.is_empty());
    }

    #[test]
    fn test_generate_with_points_200() {
        let dir = tempfile::TempDir::new().expect("tempdir creation failed");
        let spec_file = dir.path().join("large-spec.md");
        std::fs::write(&spec_file, "module: large_mod\n- MUST handle edge cases").expect("fs write failed");

        let engine = FalsifyEngine::new();
        let suite = engine
            .generate_with_points(&spec_file, TargetLanguage::Python, 200)
            .expect("unexpected failure");

        assert_eq!(suite.total_points, 200);
        assert_eq!(suite.language, TargetLanguage::Python);
        let code = suite.to_code();
        assert!(code.contains("pytest"));
        assert!(!suite.tests.is_empty());
    }

    #[test]
    fn test_generate_with_points_100_no_scaling() {
        let dir = tempfile::TempDir::new().expect("tempdir creation failed");
        let spec_file = dir.path().join("same-spec.md");
        std::fs::write(&spec_file, "module: same_mod\n- MUST be tested").expect("fs write failed");

        let engine = FalsifyEngine::new();
        let suite = engine
            .generate_with_points(&spec_file, TargetLanguage::Rust, 100)
            .expect("unexpected failure");

        // 100 points = default, no scaling needed
        assert_eq!(suite.total_points, 100);
    }

    #[test]
    fn test_generate_with_points_nonexistent_file() {
        let engine = FalsifyEngine::new();
        let result = engine.generate_with_points(
            std::path::Path::new("/nonexistent/spec.md"),
            TargetLanguage::Rust,
            50,
        );
        assert!(result.is_err());
    }

    // =====================================================================
    // Coverage: GeneratedSuite code generation with multiple categories
    // =====================================================================

    #[test]
    fn test_rust_code_multiple_categories_same_category() {
        let suite = GeneratedSuite {
            spec_name: "multi".to_string(),
            language: TargetLanguage::Rust,
            tests: vec![
                GeneratedTest {
                    id: "BC-001".to_string(),
                    name: "First boundary".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::High,
                    code: "// bc1".to_string(),
                },
                GeneratedTest {
                    id: "BC-002".to_string(),
                    name: "Second boundary".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::Medium,
                    code: "// bc2".to_string(),
                },
            ],
            total_points: 8,
        };
        let code = suite.to_code();
        // Category header should appear only once
        let boundary_count = code.matches("BOUNDARY").count();
        assert_eq!(boundary_count, 1, "BOUNDARY header should appear once");
        assert!(code.contains("BC-001"));
        assert!(code.contains("BC-002"));
        assert!(code.contains("Points: 4"));
    }

    #[test]
    fn test_python_code_multiple_categories() {
        let suite = GeneratedSuite {
            spec_name: "pytest".to_string(),
            language: TargetLanguage::Python,
            tests: vec![
                GeneratedTest {
                    id: "BC-001".to_string(),
                    name: "Boundary".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::High,
                    code: "    pass".to_string(),
                },
                GeneratedTest {
                    id: "INV-001".to_string(),
                    name: "Invariant".to_string(),
                    category: "invariant".to_string(),
                    points: 5,
                    severity: TestSeverity::Critical,
                    code: "    pass".to_string(),
                },
                GeneratedTest {
                    id: "INV-002".to_string(),
                    name: "Invariant2".to_string(),
                    category: "invariant".to_string(),
                    points: 5,
                    severity: TestSeverity::High,
                    code: "    pass".to_string(),
                },
            ],
            total_points: 14,
        };
        let code = suite.to_code();
        assert!(code.contains("BOUNDARY"));
        assert!(code.contains("INVARIANT"));
        assert!(code.contains("def test_bc_001"));
        assert!(code.contains("def test_inv_001"));
        assert!(code.contains("def test_inv_002"));
        // Check docstrings
        assert!(code.contains("Points: 4"));
        assert!(code.contains("Points: 5"));
    }

    #[test]
    fn test_suite_summary_multiple_categories() {
        let suite = GeneratedSuite {
            spec_name: "summary-test".to_string(),
            language: TargetLanguage::Rust,
            tests: vec![
                GeneratedTest {
                    id: "BC-001".to_string(),
                    name: "T1".to_string(),
                    category: "boundary".to_string(),
                    points: 4,
                    severity: TestSeverity::High,
                    code: String::new(),
                },
                GeneratedTest {
                    id: "NUM-001".to_string(),
                    name: "T2".to_string(),
                    category: "numerical".to_string(),
                    points: 7,
                    severity: TestSeverity::High,
                    code: String::new(),
                },
                GeneratedTest {
                    id: "NUM-002".to_string(),
                    name: "T3".to_string(),
                    category: "numerical".to_string(),
                    points: 6,
                    severity: TestSeverity::Medium,
                    code: String::new(),
                },
            ],
            total_points: 17,
        };
        let summary = suite.summary();
        assert_eq!(summary.total_tests, 3);
        assert_eq!(summary.total_points, 17);
        assert_eq!(*summary.points_by_category.get("boundary").expect("key not found"), 4);
        assert_eq!(*summary.points_by_category.get("numerical").expect("key not found"), 13);
    }

    #[test]
    fn test_generated_suite_empty_tests() {
        let suite = GeneratedSuite {
            spec_name: "empty".to_string(),
            language: TargetLanguage::Rust,
            tests: vec![],
            total_points: 0,
        };
        let code = suite.to_code();
        assert!(code.contains("empty"));
        assert!(code.contains("Total Points: 0"));
        let summary = suite.summary();
        assert_eq!(summary.total_tests, 0);
        assert!(summary.points_by_category.is_empty());
    }

    #[test]
    fn test_engine_template_accessor() {
        let engine = FalsifyEngine::new();
        let template = engine.template();
        assert_eq!(template.total_points(), 100);
        assert!(!template.categories.is_empty());
        // Test get_category
        assert!(template.get_category("boundary").is_some());
        assert!(template.get_category("nonexistent").is_none());
    }
}
