#!/usr/bin/env python3
"""
Fenrir PEP8 Validation and Auto-Fix Tool

Validates Python code style using flake8 and applies safe automatic fixes.
Designed to work with Fenrir's existing codebase while respecting timing-critical code.

Usage:
    python3 tools/validate_pep8.py                    # Check all Python files
    python3 tools/validate_pep8.py --fix-safe        # Auto-fix safe issues
    python3 tools/validate_pep8.py --check-only      # Exit with error if issues found
    python3 tools/validate_pep8.py --staged          # Check only staged files
"""

import os
import sys
import argparse
import subprocess
import tempfile
from pathlib import Path


class PEP8Validator:
    def __init__(self, verbose=True):
        self.verbose = verbose
        self.errors = []
        self.warnings = []
        self.fixes_applied = []
        
    def log(self, message, level="INFO"):
        """Log a message with appropriate formatting."""
        if not self.verbose and level == "INFO":
            return
            
        colors = {
            "INFO": "\033[0;36m",     # Cyan
            "SUCCESS": "\033[0;32m",  # Green
            "WARNING": "\033[1;33m",  # Yellow
            "ERROR": "\033[0;31m",    # Red
            "HEADER": "\033[1;34m",   # Bold Blue
        }
        
        reset = "\033[0m"
        color = colors.get(level, "")
        
        if level == "HEADER":
            print(f"\n{color}{'='*60}")
            print(f"{message}")
            print(f"{'='*60}{reset}")
        else:
            symbol = {
                "SUCCESS": "✓",
                "ERROR": "✗", 
                "WARNING": "⚠",
                "INFO": "•"
            }.get(level, "•")
            
            print(f"{color}{symbol} {message}{reset}")
    
    def check_flake8_available(self):
        """Check if flake8 is available."""
        try:
            result = subprocess.run(["flake8", "--version"], 
                                  capture_output=True, text=True, timeout=5)
            if result.returncode == 0:
                version = result.stdout.strip().split('\n')[0]
                self.log(f"Using flake8: {version}")
                return True
            else:
                return False
        except (subprocess.TimeoutExpired, FileNotFoundError):
            return False
    
    def get_python_files(self, directory=None, staged_only=False):
        """Get list of Python files to check."""
        if staged_only:
            try:
                result = subprocess.run([
                    "git", "diff", "--cached", "--name-only", "--diff-filter=ACM"
                ], capture_output=True, text=True, timeout=10)
                
                if result.returncode == 0:
                    files = [f for f in result.stdout.strip().split('\n') 
                            if f.endswith('.py') and Path(f).exists()]
                    return [Path(f) for f in files if f]
                else:
                    self.warnings.append("Could not get staged files, checking all files")
                    staged_only = False
            except subprocess.TimeoutExpired:
                self.warnings.append("Git command timed out, checking all files")
                staged_only = False
        
        if not staged_only:
            directory = Path(directory or "src/fenrirscreenreader")
            if not directory.exists():
                self.errors.append(f"Directory {directory} does not exist")
                return []
            
            python_files = list(directory.rglob("*.py"))
            # Filter out cache and build directories
            python_files = [f for f in python_files if not any(
                part.startswith(('__pycache__', '.git', 'build', 'dist')) 
                for part in f.parts)]
            return python_files
        
        return []
    
    def run_flake8(self, files, select=None, ignore=None):
        """Run flake8 on the given files."""
        if not files:
            return True, ""
        
        cmd = ["flake8"]
        
        if select:
            cmd.extend(["--select", select])
        if ignore:
            cmd.extend(["--ignore", ignore])
        
        # Add files
        cmd.extend([str(f) for f in files])
        
        try:
            result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
            return result.returncode == 0, result.stdout
        except subprocess.TimeoutExpired:
            self.errors.append("flake8 command timed out")
            return False, ""
        except Exception as e:
            self.errors.append(f"Failed to run flake8: {e}")
            return False, ""
    
    def categorize_issues(self, flake8_output):
        """Categorize flake8 issues by severity and safety for auto-fixing."""
        lines = flake8_output.strip().split('\n')
        issues = {'critical': [], 'safe_fixable': [], 'manual': []}
        
        for line in lines:
            if not line.strip():
                continue
                
            # Parse flake8 output: filename:line:col: code message
            parts = line.split(':', 3)
            if len(parts) < 4:
                continue
                
            filename = parts[0]
            line_num = parts[1]
            col = parts[2]
            code_msg = parts[3].strip()
            
            code = code_msg.split()[0] if code_msg else ""
            
            # Categorize by error code
            if code.startswith('E9') or code.startswith('F'):
                # Critical syntax/import errors
                issues['critical'].append(line)
            elif code in ['E111', 'E114', 'E117', 'E121', 'E122', 'E123', 'E124', 
                         'E125', 'E126', 'E127', 'E128', 'E129', 'E131', 'E133',
                         'W291', 'W292', 'W293']:
                # Safe indentation and whitespace fixes
                # But skip timing-critical files
                if not any(critical in filename.lower() for critical in 
                          ['evdevdriver', 'vcsadriver', 'screenmanager', 'inputmanager']):
                    issues['safe_fixable'].append(line)
                else:
                    issues['manual'].append(line)
            else:
                # Everything else needs manual review
                issues['manual'].append(line)
        
        return issues
    
    def apply_safe_fixes(self, files):
        """Apply safe automatic fixes using autopep8."""
        try:
            # Check if autopep8 is available
            result = subprocess.run(["autopep8", "--version"], 
                                  capture_output=True, text=True, timeout=5)
            if result.returncode != 0:
                self.warnings.append("autopep8 not available for auto-fixing")
                return False
        except (subprocess.TimeoutExpired, FileNotFoundError):
            self.warnings.append("autopep8 not available for auto-fixing")
            return False
        
        fixed_count = 0
        
        for file_path in files:
            # Skip timing-critical files
            if any(critical in str(file_path).lower() for critical in 
                  ['evdevdriver', 'vcsadriver', 'screenmanager', 'inputmanager']):
                self.log(f"Skipping timing-critical file: {file_path}", "WARNING")
                continue
            
            try:
                # Apply safe fixes only
                cmd = [
                    "autopep8", 
                    "--in-place",
                    "--select", "E111,E114,E117,E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E133,W291,W292,W293",
                    str(file_path)
                ]
                
                result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
                if result.returncode == 0:
                    self.fixes_applied.append(f"Applied safe PEP8 fixes to {file_path}")
                    fixed_count += 1
                else:
                    self.warnings.append(f"Could not auto-fix {file_path}: {result.stderr}")
                    
            except subprocess.TimeoutExpired:
                self.warnings.append(f"Auto-fix timed out for {file_path}")
            except Exception as e:
                self.warnings.append(f"Error auto-fixing {file_path}: {e}")
        
        return fixed_count > 0
    
    def validate_files(self, files, fix_safe=False):
        """Validate Python files for PEP8 compliance."""
        if not files:
            self.log("No Python files to validate")
            return True
        
        self.log(f"Validating {len(files)} Python files")
        
        # Run comprehensive flake8 check
        success, output = self.run_flake8(files)
        
        if not output.strip():
            self.log("All files pass PEP8 validation", "SUCCESS")
            return True
        
        # Categorize issues
        issues = self.categorize_issues(output)
        
        # Report critical issues
        if issues['critical']:
            self.log(f"Critical issues found ({len(issues['critical'])}):", "ERROR")
            for issue in issues['critical'][:10]:  # Limit output
                self.log(f"  {issue}", "ERROR")
            if len(issues['critical']) > 10:
                self.log(f"  ... and {len(issues['critical']) - 10} more", "ERROR")
        
        # Handle safe fixable issues
        if issues['safe_fixable']:
            if fix_safe:
                self.log(f"Auto-fixing {len(issues['safe_fixable'])} safe issues...", "INFO")
                # Get unique files from safe_fixable issues
                fix_files = set()
                for issue in issues['safe_fixable']:
                    filename = issue.split(':')[0]
                    fix_files.add(Path(filename))
                
                if self.apply_safe_fixes(fix_files):
                    self.log("Safe auto-fixes applied", "SUCCESS")
                    # Re-run flake8 to see remaining issues
                    success, output = self.run_flake8(files)
                    if output.strip():
                        remaining_issues = self.categorize_issues(output)
                        issues = remaining_issues
                    else:
                        issues = {'critical': [], 'safe_fixable': [], 'manual': []}
            else:
                self.log(f"Safe fixable issues found ({len(issues['safe_fixable'])}):", "WARNING")
                for issue in issues['safe_fixable'][:5]:
                    self.log(f"  {issue}", "WARNING")
                if len(issues['safe_fixable']) > 5:
                    self.log(f"  ... and {len(issues['safe_fixable']) - 5} more", "WARNING")
                self.log("Run with --fix-safe to auto-fix these", "INFO")
        
        # Report manual issues
        if issues['manual']:
            self.log(f"Manual review needed ({len(issues['manual'])}):", "WARNING")
            for issue in issues['manual'][:5]:
                self.log(f"  {issue}", "WARNING")
            if len(issues['manual']) > 5:
                self.log(f"  ... and {len(issues['manual']) - 5} more", "WARNING")
        
        # Return success if only manual issues remain (non-critical)
        return len(issues['critical']) == 0
    
    def generate_report(self):
        """Generate final validation report."""
        total_issues = len(self.errors) + len(self.warnings)
        
        if self.fixes_applied:
            self.log(f"\nAUTO-FIXES APPLIED ({len(self.fixes_applied)}):", "HEADER")
            for fix in self.fixes_applied:
                self.log(fix, "SUCCESS")
        
        if self.errors:
            self.log(f"\nERRORS ({len(self.errors)}):", "HEADER")
            for error in self.errors:
                self.log(error, "ERROR")
        
        if self.warnings:
            self.log(f"\nWARNINGS ({len(self.warnings)}):", "HEADER")
            for warning in self.warnings:
                self.log(warning, "WARNING")
        
        if len(self.errors) == 0:
            self.log("\n✅ PEP8 VALIDATION PASSED", "SUCCESS")
            if self.warnings:
                self.log("Non-critical style issues found - consider manual review", "INFO")
            return True
        else:
            self.log("\n❌ PEP8 VALIDATION FAILED", "ERROR")
            self.log("Critical issues must be fixed", "ERROR")
            return False


def main():
    parser = argparse.ArgumentParser(description='Validate and fix PEP8 compliance in Fenrir')
    parser.add_argument('--fix-safe', action='store_true',
                       help='Apply safe automatic fixes (avoids timing-critical files)')
    parser.add_argument('--check-only', action='store_true',
                       help='Exit with non-zero code if issues found')
    parser.add_argument('--staged', action='store_true',
                       help='Check only staged files')
    parser.add_argument('--quiet', action='store_true',
                       help='Reduce output verbosity')
    parser.add_argument('--directory', default='src/fenrirscreenreader',
                       help='Directory to scan (default: src/fenrirscreenreader)')
    
    args = parser.parse_args()
    
    validator = PEP8Validator(verbose=not args.quiet)
    
    validator.log("FENRIR PEP8 VALIDATION", "HEADER")
    
    # Check if flake8 is available
    if not validator.check_flake8_available():
        validator.log("flake8 is required but not available", "ERROR")
        validator.log("Install with: pip install flake8", "INFO")
        if args.fix_safe:
            validator.log("For auto-fixing, also install: pip install autopep8", "INFO")
        sys.exit(1)
    
    # Get files to validate
    files = validator.get_python_files(
        directory=args.directory if not args.staged else None,
        staged_only=args.staged
    )
    
    if not files:
        validator.log("No Python files found to validate")
        sys.exit(0)
    
    # Validate files
    success = validator.validate_files(files, fix_safe=args.fix_safe)
    
    # Generate report
    validation_passed = validator.generate_report()
    
    if args.check_only and not validation_passed:
        sys.exit(1)
    elif validation_passed:
        sys.exit(0)
    else:
        sys.exit(1)


if __name__ == '__main__':
    main()
