//! 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

#![allow(dead_code)]

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").unwrap().len(), 2);
        assert_eq!(by_cat.get("invariant").unwrap().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").unwrap(), 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"));
    }
}
