#!/usr/bin/env python3
"""
RIGOROUS TEST SUITE - PRD Generator
====================================
Black-box and white-box tests for automatic PRD generation.
"""

import os
import sys
import unittest
import tempfile
from pathlib import Path

# Add project root
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from core.evolution.prd_generator import PRDGenerator


class TestPRDGeneratorBlackBox(unittest.TestCase):
    """Black-box tests - external behavior."""

    def setUp(self):
        """Set up test fixtures."""
        self.temp_dir = tempfile.mkdtemp()
        self.output_dir = Path(self.temp_dir) / "prds"
        self.generator = PRDGenerator(output_dir=self.output_dir)

    def test_generate_returns_list(self):
        """T1: generate() returns a list of paths."""
        result = self.generator.generate(cycle_id=1, max_prds=2)

        self.assertIsInstance(result, list)

    def test_generate_creates_files(self):
        """T2: generate() creates actual files."""
        result = self.generator.generate(cycle_id=1, max_prds=3)

        for path in result:
            self.assertTrue(Path(path).exists(), f"File should exist: {path}")

    def test_generated_files_are_markdown(self):
        """T3: Generated files have .md extension."""
        result = self.generator.generate(cycle_id=1, max_prds=2)

        for path in result:
            self.assertTrue(str(path).endswith('.md'))

    def test_generated_files_contain_prd_structure(self):
        """T4: Generated files contain PRD structure."""
        result = self.generator.generate(cycle_id=1, category='capability', max_prds=1)

        if result:
            content = Path(result[0]).read_text()
            self.assertIn('# PRD:', content)
            self.assertIn('Vision', content)

    def test_max_prds_respected(self):
        """T5: max_prds parameter limits generation."""
        result = self.generator.generate(cycle_id=1, max_prds=1)

        self.assertLessEqual(len(result), 1)

    def test_category_parameter_respected(self):
        """T6: category parameter determines PRD type."""
        result = self.generator.generate(cycle_id=1, category='meta', max_prds=1)

        if result:
            self.assertIn('meta', Path(result[0]).stem.lower())

    def test_cycle_id_appears_in_content(self):
        """T7: cycle_id appears in generated content."""
        result = self.generator.generate(cycle_id=42, max_prds=1)

        if result:
            content = Path(result[0]).read_text()
            self.assertIn('42', content)

    def test_get_recent_prds_returns_list(self):
        """T8: get_recent_prds() returns a list."""
        self.generator.generate(cycle_id=1, max_prds=3)
        recent = self.generator.get_recent_prds()

        self.assertIsInstance(recent, list)

    def test_get_statistics_returns_dict(self):
        """T9: get_statistics() returns a dictionary."""
        self.generator.generate(cycle_id=1, max_prds=2)
        stats = self.generator.get_statistics()

        self.assertIsInstance(stats, dict)
        self.assertIn('total_prds', stats)

    def test_surprise_context_triggers_meta_prd(self):
        """T10: Providing surprise context generates meta PRD."""
        surprise = {'surprise_score': 0.8, 'trigger_reason': 'test_failures'}
        result = self.generator.generate(
            cycle_id=1,
            based_on_surprise=surprise,
            max_prds=1
        )

        if result:
            content = Path(result[0]).read_text()
            self.assertIn('Meta', content)


class TestPRDGeneratorWhiteBox(unittest.TestCase):
    """White-box tests - internal implementation."""

    def setUp(self):
        """Set up test fixtures."""
        self.temp_dir = tempfile.mkdtemp()
        self.output_dir = Path(self.temp_dir) / "prds"
        self.generator = PRDGenerator(output_dir=self.output_dir)

    def test_categories_list_populated(self):
        """W1: CATEGORIES list is not empty."""
        self.assertGreater(len(PRDGenerator.CATEGORIES), 0)

    def test_templates_dict_has_required_keys(self):
        """W2: TEMPLATES has required category keys."""
        self.assertIn('capability', PRDGenerator.TEMPLATES)
        self.assertIn('meta', PRDGenerator.TEMPLATES)

    def test_gap_targets_list_populated(self):
        """W3: GAP_TARGETS list is not empty."""
        self.assertGreater(len(PRDGenerator.GAP_TARGETS), 0)

    def test_gap_targets_have_correct_structure(self):
        """W4: GAP_TARGETS have (path, type, description) structure."""
        for target in PRDGenerator.GAP_TARGETS:
            self.assertEqual(len(target), 3)
            self.assertIsInstance(target[0], str)  # path
            self.assertIsInstance(target[1], str)  # type
            self.assertIsInstance(target[2], str)  # description

    def test_prd_count_increments(self):
        """W5: _prd_count increments with each generation."""
        initial = self.generator._prd_count

        self.generator.generate(cycle_id=1, max_prds=2)

        self.assertGreater(self.generator._prd_count, initial)

    def test_generate_single_returns_path_or_none(self):
        """W6: _generate_single returns Path or None."""
        result = self.generator._generate_single(1, 'capability', None)

        self.assertTrue(result is None or isinstance(result, Path))

    def test_generate_capability_prd_format(self):
        """W7: _generate_capability_prd returns valid markdown."""
        content = self.generator._generate_capability_prd("001_001", 1, "2026-01-23")

        self.assertIn('# PRD:', content)
        self.assertIn('GENESIS-CAP-', content)
        self.assertIn('Story 1:', content)

    def test_generate_meta_prd_format(self):
        """W8: _generate_meta_prd returns valid markdown with surprise context."""
        surprise = {'surprise_score': 0.75, 'trigger_reason': 'slow_execution'}
        content = self.generator._generate_meta_prd("001_001", 1, "2026-01-23", surprise)

        self.assertIn('# PRD:', content)
        self.assertIn('GENESIS-META-', content)
        self.assertIn('0.75', content)  # Surprise score

    def test_generate_generic_prd_format(self):
        """W9: _generate_generic_prd returns valid markdown."""
        content = self.generator._generate_generic_prd("001_001", 1, "2026-01-23", "testing")

        self.assertIn('# PRD:', content)
        self.assertIn('Testing', content)

    def test_output_directory_created_if_missing(self):
        """W10: Output directory is created if it doesn't exist."""
        new_dir = Path(self.temp_dir) / "new_prds"
        generator = PRDGenerator(output_dir=new_dir)

        self.assertTrue(new_dir.exists())

    def test_filename_format_correct(self):
        """W11: Generated filenames follow correct format."""
        result = self.generator.generate(cycle_id=1, max_prds=1)

        if result:
            filename = Path(result[0]).name
            self.assertTrue(filename.startswith('PRD_'))
            self.assertTrue(filename.endswith('.md'))

    def test_recent_prds_sorted_by_time(self):
        """W12: get_recent_prds returns files sorted by modification time."""
        import time

        self.generator.generate(cycle_id=1, max_prds=1)
        time.sleep(0.1)
        self.generator.generate(cycle_id=2, max_prds=1)

        recent = self.generator.get_recent_prds(limit=2)

        if len(recent) >= 2:
            # Most recent should be first
            self.assertGreater(recent[0].stat().st_mtime, recent[1].stat().st_mtime)


class TestPRDGeneratorEdgeCases(unittest.TestCase):
    """Edge case tests."""

    def setUp(self):
        """Set up test fixtures."""
        self.temp_dir = tempfile.mkdtemp()
        self.output_dir = Path(self.temp_dir) / "prds"
        self.generator = PRDGenerator(output_dir=self.output_dir)

    def test_zero_max_prds(self):
        """E1: max_prds=0 returns empty list."""
        result = self.generator.generate(cycle_id=1, max_prds=0)

        self.assertEqual(result, [])

    def test_negative_cycle_id(self):
        """E2: Negative cycle_id doesn't crash."""
        result = self.generator.generate(cycle_id=-1, max_prds=1)

        self.assertIsInstance(result, list)

    def test_large_cycle_id(self):
        """E3: Large cycle_id handled correctly."""
        result = self.generator.generate(cycle_id=999999, max_prds=1)

        if result:
            content = Path(result[0]).read_text()
            self.assertIn('999999', content)

    def test_invalid_category_handled(self):
        """E4: Invalid category handled gracefully."""
        # Should use the category even if not in CATEGORIES
        result = self.generator.generate(cycle_id=1, category='invalid_cat', max_prds=1)

        # Should still generate something (generic PRD)
        self.assertIsInstance(result, list)

    def test_empty_surprise_context(self):
        """E5: Empty surprise context handled."""
        result = self.generator.generate(
            cycle_id=1,
            based_on_surprise={},
            max_prds=1
        )

        self.assertIsInstance(result, list)


if __name__ == '__main__':
    unittest.main(verbosity=2)
