#!/usr/bin/env python3
"""
═══════════════════════════════════════════════════════════════
🏭 BATCH GENERATOR - ICHIGRIDEA PIPELINE
═══════════════════════════════════════════════════════════════
Génère plusieurs modules en batch avec traçabilité complète.

Usage:
    python batch_generator.py --plan S100-S110
    python batch_generator.py --pending
    python batch_generator.py --from-json batch_plan.json
    python batch_generator.py --dry-run S100-S110
"""

import json
import os
import re
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Optional
import hashlib


class BatchGenerator:
    """Génère des modules MQL5 en batch"""
    
    def __init__(self, base_dir: str = "."):
        self.base_dir = Path(base_dir)
        self.include_dir = self.base_dir / "Include"
        self.template_path = self.base_dir / "pipeline/templates/module_template.mqh"
        
        # Charger les registres
        self.modules_registry = self._load_json("MODULES_REGISTRY.json")
        self.dependencies = self._load_json("DEPENDENCIES_GRAPH.json")
        
        # Session
        self.session_id = self._generate_session_id()
        self.generated = []
        self.errors = []
        
    def _load_json(self, filename: str) -> dict:
        filepath = self.base_dir / filename
        if filepath.exists():
            with open(filepath, "r", encoding="utf-8") as f:
                return json.load(f)
        return {}
    
    def _generate_session_id(self) -> str:
        import random
        import string
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        suffix = ''.join(random.choices(string.ascii_uppercase + string.digits, k=4))
        return f"{timestamp}_{suffix}"
    
    def _calculate_sha256(self, content: str) -> str:
        return hashlib.sha256(content.encode('utf-8')).hexdigest()
    
    def parse_range(self, range_str: str) -> List[str]:
        """Parse une range de sections (S100-S110)"""
        sections = []
        
        # Pattern: S100-S110 ou S100,S101,S102
        if "-" in range_str:
            match = re.match(r'([SF])(\d+)-\1(\d+)', range_str)
            if match:
                prefix = match.group(1)
                start = int(match.group(2))
                end = int(match.group(3))
                
                for i in range(start, end + 1):
                    sections.append(f"{prefix}{i:03d}" if prefix == "S" else f"{prefix}{i:02d}")
        elif "," in range_str:
            sections = [s.strip() for s in range_str.split(",")]
        else:
            sections = [range_str]
        
        return sections
    
    def get_pending_sections(self) -> List[str]:
        """Retourne les sections en attente depuis le registre"""
        pending = []
        
        for module_name, info in self.modules_registry.get("modules", {}).items():
            status = info.get("status", "").lower()
            if status in ["pending", "todo", "à faire", ""]:
                section = info.get("section", "")
                if section:
                    pending.append(section)
        
        return sorted(pending)
    
    def get_section_info(self, section_id: str) -> Dict:
        """Récupère les infos d'une section depuis le registre"""
        for module_name, info in self.modules_registry.get("modules", {}).items():
            if info.get("section") == section_id:
                return {
                    "section_id": section_id,
                    "module_name": module_name,
                    **info
                }
        
        # Section non trouvée, créer un nom par défaut
        return {
            "section_id": section_id,
            "module_name": f"Module{section_id}",
            "description": f"Module pour section {section_id}",
            "status": "pending",
            "dependencies": []
        }
    
    def check_dependencies(self, section_id: str, info: Dict) -> List[str]:
        """Vérifie que les dépendances sont satisfaites"""
        missing = []
        deps = info.get("dependencies", [])
        
        for dep in deps:
            if isinstance(dep, str) and dep:
                dep_file = self.include_dir / f"{dep}.mqh"
                if not dep_file.exists():
                    missing.append(dep)
        
        return missing
    
    def generate_module(self, section_id: str, dry_run: bool = False) -> Dict:
        """Génère un module pour une section"""
        info = self.get_section_info(section_id)
        module_name = info.get("module_name", f"Module{section_id}")
        
        # Vérifier les dépendances
        missing_deps = self.check_dependencies(section_id, info)
        if missing_deps:
            return {
                "section_id": section_id,
                "status": "BLOCKED",
                "reason": f"Dépendances manquantes: {missing_deps}"
            }
        
        # Générer le code
        timestamp = datetime.utcnow().isoformat() + "Z"
        module_name_upper = module_name.upper().replace(" ", "_")
        
        code = f'''//+------------------------------------------------------------------+
//|                                              {module_name}.mqh   |
//|                                    Copyright 2026, SOVRALYS LLC  |
//+------------------------------------------------------------------+
// <gen:header>
//| @project     : IchiGridEA
//| @section     : {section_id}
//| @version     : 1.00
//| @generated   : {timestamp}
//| @session     : {self.session_id}
//| @model       : claude-opus-4-5-20250514
//| @spec_hash   : PENDING
//| @code_hash   : PENDING
//| @description : {info.get("description", "")}
// </gen:header>

#property copyright "Copyright 2026, SOVRALYS LLC"
#property version   "1.00"
#property strict

#ifndef __{module_name_upper}_MQH__
#define __{module_name_upper}_MQH__

// TODO: Implémenter {module_name} selon les specs {section_id}
// Spec source: Google Docs

input group "=== {module_name} ==="
input bool InpEnable{module_name.replace(" ", "")} = true;

class C{module_name.replace(" ", "")}
{{
private:
    bool m_isInitialized;
    
public:
    C{module_name.replace(" ", "")}() : m_isInitialized(false) {{}}
    ~C{module_name.replace(" ", "")}() {{ Deinit(); }}
    
    bool Init() {{ m_isInitialized = true; return true; }}
    void Deinit() {{ m_isInitialized = false; }}
    void OnTick() {{ if(!m_isInitialized) return; }}
}};

#endif // __{module_name_upper}_MQH__
'''
        
        code_hash = self._calculate_sha256(code)
        code = code.replace("@code_hash   : PENDING", f"@code_hash   : {code_hash}")
        
        output_path = self.include_dir / f"{module_name}.mqh"
        
        result = {
            "section_id": section_id,
            "module_name": module_name,
            "output_path": str(output_path),
            "code_hash": code_hash,
            "lines": len(code.split("\n")),
            "session_id": self.session_id,
            "timestamp": timestamp,
            "status": "DRY_RUN" if dry_run else "GENERATED"
        }
        
        if dry_run:
            print(f"  🔍 [DRY-RUN] {section_id} → {module_name}")
        else:
            # Créer le fichier
            with open(output_path, "w", encoding="utf-8") as f:
                f.write(code)
            print(f"  ✅ {section_id} → {module_name} ({result['lines']} lignes)")
            self.generated.append(result)
        
        return result
    
    def generate_batch(self, sections: List[str], dry_run: bool = False) -> Dict:
        """Génère un batch de modules"""
        print(f"\n{'🔍 DRY-RUN' if dry_run else '🏭 GÉNÉRATION'} - Session: {self.session_id}")
        print(f"Sections: {len(sections)}")
        print("-" * 60)
        
        results = []
        
        for section_id in sections:
            result = self.generate_module(section_id, dry_run)
            results.append(result)
        
        # Résumé
        generated = [r for r in results if r.get("status") == "GENERATED"]
        blocked = [r for r in results if r.get("status") == "BLOCKED"]
        
        print("-" * 60)
        print(f"✅ Générés: {len(generated)}")
        print(f"❌ Bloqués: {len(blocked)}")
        
        # Sauvegarder le manifest
        if not dry_run and generated:
            self._save_manifest(results)
        
        return {
            "session_id": self.session_id,
            "total": len(sections),
            "generated": len(generated),
            "blocked": len(blocked),
            "results": results
        }
    
    def _save_manifest(self, results: List[Dict]):
        """Sauvegarde le manifest de la session"""
        manifest = {
            "session_id": self.session_id,
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "results": results
        }
        
        manifest_path = self.base_dir / f"pipeline/reports/MANIFEST_{self.session_id}.json"
        manifest_path.parent.mkdir(parents=True, exist_ok=True)
        
        with open(manifest_path, "w", encoding="utf-8") as f:
            json.dump(manifest, f, indent=2)
        
        print(f"\n📄 Manifest: {manifest_path}")
    
    def plan_batch(self, sections: List[str]) -> Dict:
        """Planifie un batch sans exécuter"""
        plan = {
            "session_id": self.session_id,
            "sections": [],
            "blocked": [],
            "ready": []
        }
        
        for section_id in sections:
            info = self.get_section_info(section_id)
            missing_deps = self.check_dependencies(section_id, info)
            
            section_plan = {
                "section_id": section_id,
                "module_name": info.get("module_name"),
                "dependencies": info.get("dependencies", []),
                "missing_deps": missing_deps,
                "can_generate": len(missing_deps) == 0
            }
            
            plan["sections"].append(section_plan)
            
            if section_plan["can_generate"]:
                plan["ready"].append(section_id)
            else:
                plan["blocked"].append(section_id)
        
        return plan


def main():
    import sys
    
    generator = BatchGenerator()
    
    if "--plan" in sys.argv:
        try:
            idx = sys.argv.index("--plan")
            range_str = sys.argv[idx + 1]
            
            sections = generator.parse_range(range_str)
            plan = generator.plan_batch(sections)
            
            print(f"📋 Plan de batch: {len(sections)} sections")
            print(f"   ✅ Prêts: {len(plan['ready'])}")
            print(f"   ❌ Bloqués: {len(plan['blocked'])}")
            
            if plan['blocked']:
                print("\n   Sections bloquées:")
                for s in plan['sections']:
                    if not s['can_generate']:
                        print(f"     - {s['section_id']}: manque {s['missing_deps']}")
                        
        except IndexError:
            print("Usage: python batch_generator.py --plan S100-S110")
            
    elif "--dry-run" in sys.argv:
        try:
            idx = sys.argv.index("--dry-run")
            range_str = sys.argv[idx + 1]
            
            sections = generator.parse_range(range_str)
            generator.generate_batch(sections, dry_run=True)
            
        except IndexError:
            print("Usage: python batch_generator.py --dry-run S100-S110")
            
    elif "--generate" in sys.argv:
        try:
            idx = sys.argv.index("--generate")
            range_str = sys.argv[idx + 1]
            
            sections = generator.parse_range(range_str)
            generator.generate_batch(sections, dry_run=False)
            
        except IndexError:
            print("Usage: python batch_generator.py --generate S100-S110")
            
    elif "--pending" in sys.argv:
        pending = generator.get_pending_sections()
        print(f"📋 Sections en attente: {len(pending)}")
        for s in pending[:20]:
            print(f"   - {s}")
        if len(pending) > 20:
            print(f"   ... et {len(pending) - 20} autres")
            
    else:
        print(__doc__)


if __name__ == "__main__":
    main()
