#!/usr/bin/env python3
"""
═══════════════════════════════════════════════════════════════
🔍 MQL5 LINTER - ICHIGRIDEA PIPELINE
═══════════════════════════════════════════════════════════════
Vérifie la qualité et les erreurs courantes du code MQL5.

Usage:
    python mql5_linter.py --lint Include/
    python mql5_linter.py --lint Include/SessionPilot.mqh
    python mql5_linter.py --fix Include/
    python mql5_linter.py --report
"""

import re
import os
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Tuple
from dataclasses import dataclass
from enum import Enum


class Severity(Enum):
    ERROR = "ERROR"
    WARNING = "WARNING"
    INFO = "INFO"
    STYLE = "STYLE"


@dataclass
class LintIssue:
    file: str
    line: int
    column: int
    severity: Severity
    rule: str
    message: str
    suggestion: str = ""


class MQL5Linter:
    """Linter pour code MQL5"""
    
    def __init__(self):
        self.issues: List[LintIssue] = []
        self.files_checked = 0
        
        # Règles de lint
        self.rules = {
            # Erreurs critiques
            "E001": ("Handle non libéré", Severity.ERROR),
            "E002": ("GetLastError sans ResetLastError", Severity.ERROR),
            "E003": ("Division potentielle par zéro", Severity.ERROR),
            "E004": ("Boucle infinie potentielle", Severity.ERROR),
            "E005": ("Return manquant", Severity.ERROR),
            
            # Warnings
            "W001": ("Variable non utilisée", Severity.WARNING),
            "W002": ("Magic number", Severity.WARNING),
            "W003": ("Fonction trop longue (>100 lignes)", Severity.WARNING),
            "W004": ("Trop de paramètres (>7)", Severity.WARNING),
            "W005": ("TODO/FIXME trouvé", Severity.WARNING),
            
            # Style
            "S001": ("Nom de variable non conforme", Severity.STYLE),
            "S002": ("Indentation incorrecte", Severity.STYLE),
            "S003": ("Ligne trop longue (>120 chars)", Severity.STYLE),
            "S004": ("Espace manquant après virgule", Severity.STYLE),
            "S005": ("Commentaire manquant sur fonction", Severity.STYLE),
        }
    
    def lint_file(self, filepath: Path) -> List[LintIssue]:
        """Lint un fichier MQL5"""
        issues = []
        
        try:
            with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
                content = f.read()
                lines = content.split("\n")
        except Exception as e:
            issues.append(LintIssue(
                file=str(filepath),
                line=0,
                column=0,
                severity=Severity.ERROR,
                rule="E000",
                message=f"Impossible de lire le fichier: {e}"
            ))
            return issues
        
        self.files_checked += 1
        
        # Vérifications ligne par ligne
        for line_num, line in enumerate(lines, 1):
            issues.extend(self._check_line(filepath, line_num, line))
        
        # Vérifications globales
        issues.extend(self._check_global(filepath, content, lines))
        
        return issues
    
    def _check_line(self, filepath: Path, line_num: int, line: str) -> List[LintIssue]:
        """Vérifie une ligne"""
        issues = []
        
        # S003: Ligne trop longue
        if len(line) > 120:
            issues.append(LintIssue(
                file=str(filepath),
                line=line_num,
                column=121,
                severity=Severity.STYLE,
                rule="S003",
                message=f"Ligne trop longue ({len(line)} > 120 caractères)"
            ))
        
        # W002: Magic numbers
        magic_pattern = r'[=<>!+\-*/]\s*(\d{3,})[^.]\s*[;,)\]]'
        match = re.search(magic_pattern, line)
        if match and not "magic" in line.lower() and not "//" in line[:line.find(match.group(0))]:
            issues.append(LintIssue(
                file=str(filepath),
                line=line_num,
                column=match.start(),
                severity=Severity.WARNING,
                rule="W002",
                message=f"Magic number: {match.group(1)}",
                suggestion="Utiliser une constante nommée"
            ))
        
        # W005: TODO/FIXME
        if re.search(r'\b(TODO|FIXME|XXX|HACK)\b', line, re.IGNORECASE):
            issues.append(LintIssue(
                file=str(filepath),
                line=line_num,
                column=0,
                severity=Severity.WARNING,
                rule="W005",
                message="TODO/FIXME trouvé"
            ))
        
        # E002: GetLastError sans ResetLastError
        if "GetLastError()" in line:
            # Vérifier si ResetLastError est appelé avant
            # (simplification: on vérifie juste dans la même ligne ou ligne précédente)
            issues.append(LintIssue(
                file=str(filepath),
                line=line_num,
                column=line.find("GetLastError"),
                severity=Severity.WARNING,  # WARNING car on ne peut pas vérifier le contexte complet
                rule="E002",
                message="GetLastError() trouvé - vérifier que ResetLastError() est appelé avant",
                suggestion="Ajouter ResetLastError() avant GetLastError()"
            ))
        
        # E003: Division par zéro potentielle
        div_pattern = r'/\s*([a-zA-Z_]\w*)\s*[;,)\]]'
        match = re.search(div_pattern, line)
        if match and not "//" in line[:line.find(match.group(0))]:
            var = match.group(1)
            if var not in ["100", "10", "2", "1000"]:  # Ignorer les constantes communes
                issues.append(LintIssue(
                    file=str(filepath),
                    line=line_num,
                    column=match.start(),
                    severity=Severity.INFO,
                    rule="E003",
                    message=f"Division par '{var}' - vérifier que ce n'est pas zéro",
                    suggestion=f"Ajouter une vérification: if({var} != 0)"
                ))
        
        return issues
    
    def _check_global(self, filepath: Path, content: str, lines: List[str]) -> List[LintIssue]:
        """Vérifications globales du fichier"""
        issues = []
        
        # E001: Handles non libérés
        # Compter les créations et libérations
        handle_creates = len(re.findall(r'\b(iCustom|iATR|iADX|iRSI|iMACD|iBands|iIchimoku)\s*\(', content))
        handle_releases = len(re.findall(r'IndicatorRelease\s*\(', content))
        
        if handle_creates > handle_releases:
            issues.append(LintIssue(
                file=str(filepath),
                line=0,
                column=0,
                severity=Severity.WARNING,
                rule="E001",
                message=f"Handles potentiellement non libérés: {handle_creates} créés, {handle_releases} libérés",
                suggestion="Vérifier que chaque handle est libéré dans OnDeinit()"
            ))
        
        # W003: Fonctions trop longues
        in_function = False
        function_start = 0
        function_name = ""
        brace_count = 0
        
        for line_num, line in enumerate(lines, 1):
            # Détecter début de fonction
            func_match = re.match(r'^\s*(bool|int|double|void|string)\s+(\w+)\s*\(', line)
            if func_match and not in_function:
                function_name = func_match.group(2)
                function_start = line_num
                in_function = True
                brace_count = 0
            
            # Compter les accolades
            if in_function:
                brace_count += line.count("{") - line.count("}")
                
                if brace_count == 0 and "{" in content[sum(len(l)+1 for l in lines[:function_start])]:
                    function_length = line_num - function_start
                    if function_length > 100:
                        issues.append(LintIssue(
                            file=str(filepath),
                            line=function_start,
                            column=0,
                            severity=Severity.WARNING,
                            rule="W003",
                            message=f"Fonction '{function_name}' trop longue ({function_length} lignes > 100)",
                            suggestion="Diviser en fonctions plus petites"
                        ))
                    in_function = False
        
        return issues
    
    def lint_directory(self, dirpath: Path) -> List[LintIssue]:
        """Lint tous les fichiers d'un répertoire"""
        all_issues = []
        
        for filepath in dirpath.glob("*.mqh"):
            issues = self.lint_file(filepath)
            all_issues.extend(issues)
            self.issues.extend(issues)
        
        for filepath in dirpath.glob("*.mq5"):
            issues = self.lint_file(filepath)
            all_issues.extend(issues)
            self.issues.extend(issues)
        
        return all_issues
    
    def get_summary(self) -> Dict:
        """Retourne un résumé du lint"""
        return {
            "files_checked": self.files_checked,
            "total_issues": len(self.issues),
            "errors": len([i for i in self.issues if i.severity == Severity.ERROR]),
            "warnings": len([i for i in self.issues if i.severity == Severity.WARNING]),
            "style": len([i for i in self.issues if i.severity == Severity.STYLE]),
            "info": len([i for i in self.issues if i.severity == Severity.INFO]),
        }
    
    def generate_report(self) -> str:
        """Génère un rapport markdown"""
        summary = self.get_summary()
        
        report = f"""# 🔍 Rapport de Lint MQL5

**Date**: {datetime.utcnow().isoformat()}Z
**Fichiers vérifiés**: {summary['files_checked']}

## 📊 Résumé

| Sévérité | Nombre |
|----------|--------|
| ❌ Erreurs | {summary['errors']} |
| ⚠️ Warnings | {summary['warnings']} |
| 📝 Style | {summary['style']} |
| ℹ️ Info | {summary['info']} |
| **Total** | **{summary['total_issues']}** |

## 📋 Issues par fichier

"""
        # Grouper par fichier
        by_file = {}
        for issue in self.issues:
            if issue.file not in by_file:
                by_file[issue.file] = []
            by_file[issue.file].append(issue)
        
        for filepath, issues in sorted(by_file.items()):
            report += f"\n### {Path(filepath).name}\n\n"
            for issue in issues:
                severity_emoji = {
                    Severity.ERROR: "❌",
                    Severity.WARNING: "⚠️",
                    Severity.STYLE: "📝",
                    Severity.INFO: "ℹ️"
                }[issue.severity]
                
                report += f"- {severity_emoji} **L{issue.line}** [{issue.rule}] {issue.message}\n"
                if issue.suggestion:
                    report += f"  - 💡 {issue.suggestion}\n"
        
        return report


def main():
    import sys
    
    linter = MQL5Linter()
    
    if "--lint" in sys.argv:
        try:
            idx = sys.argv.index("--lint")
            target = sys.argv[idx + 1]
            
            target_path = Path(target)
            
            if target_path.is_dir():
                issues = linter.lint_directory(target_path)
            else:
                issues = linter.lint_file(target_path)
            
            # Afficher les issues
            for issue in issues:
                emoji = {
                    Severity.ERROR: "❌",
                    Severity.WARNING: "⚠️",
                    Severity.STYLE: "📝",
                    Severity.INFO: "ℹ️"
                }[issue.severity]
                print(f"{emoji} {Path(issue.file).name}:{issue.line} [{issue.rule}] {issue.message}")
            
            # Résumé
            summary = linter.get_summary()
            print(f"\n📊 {summary['files_checked']} fichiers, {summary['total_issues']} issues")
            
        except IndexError:
            print("Usage: python mql5_linter.py --lint Include/")
            
    elif "--report" in sys.argv:
        linter.lint_directory(Path("Include"))
        report = linter.generate_report()
        
        print(report)
        
        # Sauvegarder
        with open("pipeline/reports/lint_report.md", "w") as f:
            f.write(report)
        print("\n📄 Rapport sauvegardé: pipeline/reports/lint_report.md")
        
    else:
        print(__doc__)


if __name__ == "__main__":
    main()
