#!/usr/bin/env python3
"""
MarkiTect Architectural Test Runner

Executes tests in reverse dependency order for optimal feedback and debugging efficiency.
Tests are run layer by layer, with execution stopping at the first failed layer to
provide immediate feedback on architectural issues.

Usage:
    python run_architectural_tests.py          # Run all layers
    python run_architectural_tests.py --layer foundation  # Run specific layer
    python run_architectural_tests.py --list-layers       # List all architectural layers
    python run_architectural_tests.py --parallel   # Run layers in parallel (advanced)
"""

import subprocess
import sys
import time
import argparse
import os
from pathlib import Path
from typing import List, Tuple, Optional

# ANSI color codes for better output
class Colors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'

def print_colored(message: str, color: str = Colors.ENDC) -> None:
    """Print message with color."""
    print(f"{color}{message}{Colors.ENDC}")

def run_layer_tests(layer_name: str, pattern: str, verbose: bool = False) -> Tuple[bool, float, int]:
    """
    Run tests for a specific architectural layer.

    Returns:
        Tuple of (success, execution_time, test_count)
    """
    print_colored(f"\n🧪 Testing {layer_name} Layer", Colors.HEADER)
    print_colored("=" * 60, Colors.HEADER)

    # Find test files matching the pattern
    test_files = list(Path("tests").glob(pattern))
    # print_colored(f"Found {len(test_files)} test files: {[str(f) for f in test_files]}", Colors.OKBLUE)

    if not test_files:
        print_colored(f"⚠️  No test files found for pattern: {pattern}", Colors.WARNING)
        return True, 0.0, 0

    start_time = time.time()

    # Build pytest command with actual file paths
    cmd = [
        "python", "-m", "pytest",
        "--tb=short",
        "--durations=5",
        "-o", "addopts="  # Override addopts to avoid config conflicts
    ]

    # Add each test file
    for test_file in test_files:
        cmd.append(str(test_file))

    if verbose:
        cmd.append("-v")
    else:
        cmd.append("-q")

    # Add coverage if available (check if pytest-cov is installed)
    try:
        result_check = subprocess.run(["python", "-m", "pytest", "--help"],
                                     capture_output=True, text=True)
        if "--cov" in result_check.stdout:
            cmd.extend(["--cov=.", "--cov-report=term-missing"])
    except Exception:
        pass  # Coverage not available, continue without it

    # Execute tests with proper environment
    env = os.environ.copy()
    env['PYTHONPATH'] = 'src'
    result = subprocess.run(cmd, capture_output=True, text=True, env=env)
    execution_time = time.time() - start_time

    # Parse test count from output
    test_count = 0
    output_lines = result.stdout.split('\n')

    # Look for collected line pattern: "collected X items"
    for line in output_lines:
        if 'collected' in line and 'item' in line:
            try:
                words = line.split()
                collected_idx = words.index('collected')
                if collected_idx + 1 < len(words):
                    test_count = int(words[collected_idx + 1])
                    break
            except (ValueError, IndexError):
                pass

    # Alternative: count PASSED/FAILED lines
    if test_count == 0:
        test_count = len([line for line in output_lines if ' PASSED ' in line or ' FAILED ' in line or ' SKIPPED ' in line])

    success = result.returncode == 0

    # Print results
    if success:
        print_colored(f"✅ {layer_name} layer: {test_count} tests PASSED in {execution_time:.2f}s", Colors.OKGREEN)
    else:
        print_colored(f"❌ {layer_name} layer: FAILED in {execution_time:.2f}s", Colors.FAIL)
        if verbose or not success:
            print_colored("STDOUT:", Colors.WARNING)
            print(result.stdout)
            print_colored("STDERR:", Colors.WARNING)
            print(result.stderr)

    return success, execution_time, test_count

def check_test_files_exist(layers: List[Tuple[str, str]]) -> List[str]:
    """Check which test files exist for each layer."""
    missing_patterns = []

    for layer_name, pattern in layers:
        test_files = list(Path("tests").glob(pattern))
        if not test_files:
            missing_patterns.append(f"{layer_name} ({pattern})")

    return missing_patterns

def main():
    """Execute architectural test suite."""
    parser = argparse.ArgumentParser(description="MarkiTect Architectural Test Runner")
    parser.add_argument("--layer", help="Run specific layer only",
                       choices=["foundation", "infrastructure", "integration", "domain", "service", "application", "presentation"])
    parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
    parser.add_argument("--continue-on-failure", action="store_true", help="Continue testing even if a layer fails")
    parser.add_argument("--parallel", action="store_true", help="Run suitable layers in parallel (experimental)")
    parser.add_argument("--quick", action="store_true", help="Run only foundation and infrastructure layers")
    parser.add_argument("--list-layers", action="store_true", help="List all available architectural layers")

    args = parser.parse_args()

    # Define test execution order (reverse dependency)
    all_layers = [
        ("Foundation", "test_l7_foundation_*.py"),
        ("Infrastructure", "test_l5_infrastructure_*.py"),
        ("Integration", "test_l6_integration_*.py"),
        ("Domain", "test_l3_domain_*.py"),
        ("Service", "test_l4_service_*.py"),
        ("Application", "test_l2_application_*.py"),
        ("Presentation", "test_l1_presentation_*.py")
    ]

    # Handle --list-layers flag
    if args.list_layers:
        print_colored("🏗️  MarkiTect Architectural Layers", Colors.BOLD)
        print_colored("Execution order (reverse dependency):", Colors.OKBLUE)
        print()
        for i, (layer_name, pattern) in enumerate(all_layers, 1):
            layer_key = layer_name.lower()
            test_files = list(Path("tests").glob(pattern))
            test_count = len(test_files)

            print_colored(f"{i}. {layer_name} Layer", Colors.HEADER)
            print_colored(f"   Command: --layer {layer_key}", Colors.OKCYAN)
            print_colored(f"   Pattern: {pattern}", Colors.OKBLUE)
            print_colored(f"   Test files: {test_count}", Colors.OKGREEN)
            if test_count > 0:
                print_colored(f"   Files: {', '.join([f.name for f in test_files])}", Colors.ENDC)
            print()

        print_colored("Special Options:", Colors.HEADER)
        print_colored("   --quick           Run foundation + infrastructure only", Colors.OKCYAN)
        print_colored("   --continue-on-failure  Continue even if a layer fails", Colors.OKCYAN)
        print_colored("   --verbose         Show detailed test output", Colors.OKCYAN)
        return

    # Filter layers based on arguments
    if args.layer:
        layer_map = {
            "foundation": [("Foundation", "test_l7_foundation_*.py")],
            "infrastructure": [("Infrastructure", "test_l5_infrastructure_*.py")],
            "integration": [("Integration", "test_l6_integration_*.py")],
            "domain": [("Domain", "test_l3_domain_*.py")],
            "service": [("Service", "test_l4_service_*.py")],
            "application": [("Application", "test_l2_application_*.py")],
            "presentation": [("Presentation", "test_l1_presentation_*.py")]
        }
        layers = layer_map[args.layer]
    elif args.quick:
        layers = all_layers[:2]  # Only foundation and infrastructure
    else:
        layers = all_layers

    # Check for missing test files
    missing = check_test_files_exist(layers)
    if missing:
        print_colored(f"⚠️  Warning: No test files found for: {', '.join(missing)}", Colors.WARNING)

    # Print header
    print_colored("🏗️  MarkiTect Architectural Test Runner", Colors.BOLD)
    print_colored("Executing tests in reverse dependency order for optimal feedback...", Colors.OKBLUE)
    print_colored(f"Testing {len(layers)} architectural layers", Colors.OKBLUE)

    if not args.continue_on_failure:
        print_colored("Will stop at first layer failure (use --continue-on-failure to override)", Colors.WARNING)

    # Execute tests layer by layer
    failed_layers = []
    total_time = 0
    total_tests = 0

    for layer_name, pattern in layers:
        success, execution_time, test_count = run_layer_tests(
            layer_name, pattern, args.verbose
        )

        total_time += execution_time
        total_tests += test_count

        if not success:
            failed_layers.append(layer_name)
            if not args.continue_on_failure:
                print_colored(f"\n⚠️  Stopping execution due to {layer_name} layer failure", Colors.WARNING)
                print_colored("Fix foundation issues before testing dependent layers.", Colors.WARNING)
                break

    # Print final summary
    print_colored("\n" + "=" * 60, Colors.HEADER)

    if failed_layers:
        print_colored(f"❌ Test execution completed with {len(failed_layers)} failed layer(s):", Colors.FAIL)
        for layer in failed_layers:
            print_colored(f"   - {layer}", Colors.FAIL)
        print_colored(f"\n📊 Total: {total_tests} tests in {total_time:.2f}s", Colors.OKBLUE)

        # Provide helpful guidance
        if "Foundation" in failed_layers:
            print_colored("\n💡 Foundation layer failures indicate core system issues.", Colors.WARNING)
            print_colored("   Fix these first as they affect all dependent layers.", Colors.WARNING)
        elif "Infrastructure" in failed_layers:
            print_colored("\n💡 Infrastructure layer failures affect business logic.", Colors.WARNING)
            print_colored("   These should be prioritized after foundation issues.", Colors.WARNING)

        sys.exit(1)
    else:
        print_colored("✅ All architectural layers passed!", Colors.OKGREEN)
        print_colored("🎉 System is architecturally sound!", Colors.OKGREEN)
        print_colored(f"📊 Total: {total_tests} tests executed in {total_time:.2f}s", Colors.OKBLUE)

        # Performance feedback
        if total_time < 30:
            print_colored("⚡ Excellent performance: Tests completed in under 30 seconds!", Colors.OKGREEN)
        elif total_time < 60:
            print_colored("✅ Good performance: Tests completed in under 1 minute.", Colors.OKGREEN)
        else:
            print_colored("⏱️  Consider optimizing slow tests for better feedback loops.", Colors.WARNING)

if __name__ == "__main__":
    main()