#!/usr/bin/env python3
"""
Test runner script for the Alloy language interpreter test suite.
Runs all test modules and provides a comprehensive test report.
"""

import unittest
import sys
import os
import time
from io import StringIO

# Add the current directory to the Python path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

# Import all test modules
try:
    import test_alloy_features
    import test_parser_only
    import test_error_cases
    import test_examples
except ImportError as e:
    print(f"Error importing test modules: {e}")
    print("Make sure all test files are in the same directory as this script.")
    sys.exit(1)


class ColoredTextTestResult(unittest.TextTestResult):
    """Custom test result class with colored output"""
    
    def __init__(self, stream, descriptions, verbosity):
        super().__init__(stream, descriptions, verbosity)
        self.success_count = 0
        
    def addSuccess(self, test):
        super().addSuccess(test)
        self.success_count += 1
        if self.verbosity > 1:
            self.stream.write("✅ ")
            self.stream.flush()
    
    def addError(self, test, err):
        super().addError(test, err)
        if self.verbosity > 1:
            self.stream.write("❌ ")
            self.stream.flush()
    
    def addFailure(self, test, err):
        super().addFailure(test, err)
        if self.verbosity > 1:
            self.stream.write("❌ ")
            self.stream.flush()
    
    def addSkip(self, test, reason):
        super().addSkip(test, reason)
        if self.verbosity > 1:
            self.stream.write("⏭️  ")
            self.stream.flush()


class ColoredTextTestRunner(unittest.TextTestRunner):
    """Custom test runner with colored output"""
    
    resultclass = ColoredTextTestResult
    
    def _makeResult(self):
        return self.resultclass(self.stream, self.descriptions, self.verbosity)


def print_header(title):
    """Print a formatted header"""
    print("\n" + "=" * 80)
    print(f" {title}")
    print("=" * 80)


def print_section(title):
    """Print a formatted section header"""
    print(f"\n📋 {title}")
    print("-" * 60)


def run_test_suite(test_module, module_name, verbosity=2):
    """Run a specific test suite and return results"""
    print_section(f"Running {module_name}")
    
    # Create test loader and suite
    loader = unittest.TestLoader()
    suite = loader.loadTestsFromModule(test_module)
    
    # Create custom test runner
    stream = StringIO()
    runner = ColoredTextTestRunner(
        stream=stream,
        verbosity=verbosity,
        descriptions=True,
        failfast=False
    )
    
    # Run tests and capture results
    start_time = time.time()
    result = runner.run(suite)
    end_time = time.time()
    
    # Print results
    output = stream.getvalue()
    print(output)
    
    # Print summary
    total_tests = result.testsRun
    failures = len(result.failures)
    errors = len(result.errors)
    skipped = len(result.skipped)
    success_count = getattr(result, 'success_count', total_tests - failures - errors - skipped)
    
    print(f"📊 {module_name} Results:")
    print(f"   Total Tests: {total_tests}")
    print(f"   ✅ Passed: {success_count}")
    print(f"   ❌ Failed: {failures}")
    print(f"   💥 Errors: {errors}")
    print(f"   ⏭️  Skipped: {skipped}")
    print(f"   ⏱️  Time: {end_time - start_time:.2f}s")
    
    return result, end_time - start_time


def print_detailed_failures(all_results):
    """Print detailed information about failures and errors"""
    has_failures = False
    
    for module_name, result in all_results.items():
        if result.failures or result.errors:
            has_failures = True
            print_section(f"Detailed Results for {module_name}")
            
            if result.failures:
                print("🔥 FAILURES:")
                for test, traceback in result.failures:
                    print(f"\n❌ {test}")
                    print("-" * 40)
                    print(traceback)
            
            if result.errors:
                print("💥 ERRORS:")
                for test, traceback in result.errors:
                    print(f"\n💥 {test}")
                    print("-" * 40)
                    print(traceback)
    
    if not has_failures:
        print_section("🎉 All Tests Passed!")
        print("No failures or errors to report.")


def run_quick_validation():
    """Run a quick validation to ensure the interpreter is working"""
    print_section("Quick Validation Check")
    
    try:
        import alloy_interpreter
        
        # Test basic parsing
        code = 'x = 42'
        ast = alloy_interpreter.parser.parse(code)
        if ast is None:
            print("❌ Basic parsing failed")
            return False
        
        # Test basic interpretation
        interpreter = alloy_interpreter.Interpreter()
        interpreter.interpret(ast)
        result = interpreter.global_env.get('x')
        if result != 42:
            print(f"❌ Basic interpretation failed: expected 42, got {result}")
            return False
        
        print("✅ Quick validation passed")
        return True
        
    except Exception as e:
        print(f"❌ Quick validation failed: {e}")
        return False


def generate_test_report(all_results, total_time):
    """Generate a comprehensive test report"""
    print_header("COMPREHENSIVE TEST REPORT")
    
    # Calculate totals
    total_tests = sum(result.testsRun for result in all_results.values())
    total_failures = sum(len(result.failures) for result in all_results.values())
    total_errors = sum(len(result.errors) for result in all_results.values())
    total_skipped = sum(len(result.skipped) for result in all_results.values())
    total_passed = total_tests - total_failures - total_errors - total_skipped
    
    # Overall statistics
    print(f"📈 Overall Statistics:")
    print(f"   Total Test Modules: {len(all_results)}")
    print(f"   Total Test Cases: {total_tests}")
    print(f"   ✅ Total Passed: {total_passed}")
    print(f"   ❌ Total Failed: {total_failures}")
    print(f"   💥 Total Errors: {total_errors}")
    print(f"   ⏭️  Total Skipped: {total_skipped}")
    print(f"   ⏱️  Total Time: {total_time:.2f}s")
    
    # Success rate
    if total_tests > 0:
        success_rate = (total_passed / total_tests) * 100
        print(f"   📊 Success Rate: {success_rate:.1f}%")
    
    print("\n🔍 Module Breakdown:")
    for module_name, result in all_results.items():
        passed = result.testsRun - len(result.failures) - len(result.errors) - len(result.skipped)
        if result.testsRun > 0:
            rate = (passed / result.testsRun) * 100
            print(f"   {module_name}: {passed}/{result.testsRun} ({rate:.1f}%)")
        else:
            print(f"   {module_name}: No tests found")
    
    # Overall assessment
    print(f"\n🎯 Overall Assessment:")
    if total_failures == 0 and total_errors == 0:
        print("   🌟 EXCELLENT: All tests passed!")
        if total_skipped > 0:
            print(f"   ℹ️  Note: {total_skipped} tests were skipped")
    elif total_failures + total_errors < total_tests * 0.1:  # Less than 10% failure rate
        print("   👍 GOOD: Most tests passed with minor issues")
    elif total_failures + total_errors < total_tests * 0.3:  # Less than 30% failure rate
        print("   ⚠️  FAIR: Some tests failed, needs attention")
    else:
        print("   🚨 NEEDS WORK: Many tests failed, significant issues detected")


def main():
    """Main test runner function"""
    print_header("🧪 ALLOY LANGUAGE INTERPRETER TEST SUITE")
    print("Running comprehensive tests for all Alloy language features...")
    
    # Quick validation first
    if not run_quick_validation():
        print("\n❌ Quick validation failed. Aborting full test suite.")
        print("Please check that alloy_interpreter.py is working correctly.")
        return 1
    
    # Test modules to run
    test_modules = [
        (test_parser_only, "Parser Tests"),
        (test_alloy_features, "Core Features Tests"),
        (test_error_cases, "Error Handling Tests"),
        (test_examples, "Specification Examples Tests"),
    ]
    
    # Run all test suites
    all_results = {}
    total_start_time = time.time()
    
    for test_module, module_name in test_modules:
        try:
            result, duration = run_test_suite(test_module, module_name)
            all_results[module_name] = result
        except Exception as e:
            print(f"❌ Failed to run {module_name}: {e}")
            # Create a dummy result for failed modules
            dummy_result = unittest.TestResult()
            dummy_result.testsRun = 0
            all_results[module_name] = dummy_result
    
    total_end_time = time.time()
    total_time = total_end_time - total_start_time
    
    # Generate comprehensive report
    generate_test_report(all_results, total_time)
    
    # Print detailed failures if any
    print_detailed_failures(all_results)
    
    # Return appropriate exit code
    total_failures = sum(len(result.failures) for result in all_results.values())
    total_errors = sum(len(result.errors) for result in all_results.values())
    
    if total_failures == 0 and total_errors == 0:
        print_header("🎉 ALL TESTS COMPLETED SUCCESSFULLY!")
        return 0
    else:
        print_header("⚠️  TESTS COMPLETED WITH ISSUES")
        print("Please review the detailed results above and fix any failing tests.")
        return 1


def run_specific_module(module_name):
    """Run tests for a specific module only"""
    module_map = {
        'parser': test_parser_only,
        'features': test_alloy_features,
        'errors': test_error_cases,
        'examples': test_examples,
    }
    
    if module_name not in module_map:
        print(f"Unknown module: {module_name}")
        print(f"Available modules: {', '.join(module_map.keys())}")
        return 1
    
    print_header(f"🧪 RUNNING {module_name.upper()} TESTS ONLY")
    
    test_module = module_map[module_name]
    result, duration = run_test_suite(test_module, f"{module_name} tests")
    
    # Simple report for single module
    print(f"\n📊 Final Result:")
    if len(result.failures) == 0 and len(result.errors) == 0:
        print("✅ All tests passed!")
        return 0
    else:
        print(f"❌ {len(result.failures)} failures, {len(result.errors)} errors")
        return 1


if __name__ == '__main__':
    # Check for command line arguments
    if len(sys.argv) > 1:
        module_name = sys.argv[1]
        exit_code = run_specific_module(module_name)
    else:
        # Show usage information
        print("🧪 Alloy Test Runner")
        print("\nUsage:")
        print("  python run_all_tests.py           # Run all tests")
        print("  python run_all_tests.py parser    # Run only parser tests")
        print("  python run_all_tests.py features  # Run only core features tests")
        print("  python run_all_tests.py errors    # Run only error handling tests")
        print("  python run_all_tests.py examples  # Run only specification examples tests")
        print("\nRunning all tests by default...\n")
        
        exit_code = main()
    
    sys.exit(exit_code)