#!/usr/bin/env python3
"""
╔══════════════════════════════════════════════════════════════════════════════╗
║               ICHIGRIDEA AUTONOMOUS PIPELINE v5.0                            ║
║                                                                              ║
║  USAGE:                                                                      ║
║    python ICHIGRID_PIPELINE.py                   # Mode autonome complet     ║
║    python ICHIGRID_PIPELINE.py status            # État du projet            ║
║    python ICHIGRID_PIPELINE.py wire              # Câbler modules orphelins  ║
║    python ICHIGRID_PIPELINE.py fix               # Corriger tous modules     ║
║    python ICHIGRID_PIPELINE.py deploy            # Déployer GitHub + OVH     ║
║    python ICHIGRID_PIPELINE.py checkpoint [msg]  # Créer checkpoint          ║
║    python ICHIGRID_PIPELINE.py generate S120 Nom # Générer nouveau module    ║
╚══════════════════════════════════════════════════════════════════════════════╝
"""

import os, sys, json, hashlib, shutil, re, subprocess
from datetime import datetime, timezone
from pathlib import Path

CONFIG = {
    "version": "5.0",
    "github": "https://github.com/KinSushi/IchiGrid-EA.git",
    "branch": "main",
    "ovh_ip": "92.222.226.50",
    "ovh_user": "ubuntu", 
    "ovh_key": "~/.ssh/ovh_serveur",
    "ovh_path": "/opt/ichigrid-mirror",
    "model": "claude-opus-4-5-20250514",
}

# ════════════════════════════════════════════════════════════════════════════════
# TEMPLATE MODULE
# ════════════════════════════════════════════════════════════════════════════════
TEMPLATE = '''//+------------------------------------------------------------------+
//|                                              {MODULE_NAME}.mqh   |
//|                                    Copyright 2026, SOVRALYS LLC  |
//|                                         https://www.sovralys.com |
//+------------------------------------------------------------------+
// <gen:header>
//| @project     : IchiGridEA
//| @section     : {SECTION_ID}
//| @version     : 1.00
//| @generated   : {TIMESTAMP}
//| @session     : {SESSION}
//| @model       : {MODEL}
//| @spec_hash   : N/A
//| @code_hash   : {CODE_HASH}
//| @spec_source : N/A
//| @depends     : {DEPS}
//| @description : {DESC}
// </gen:header>

#property copyright "Copyright 2026, SOVRALYS LLC"
#property link      "https://www.sovralys.com"
#property version   "1.00"
#property strict

#ifndef __{MODULE_UPPER}_MQH__
#define __{MODULE_UPPER}_MQH__

#include "CommonDefinitions.mqh"

input group "=== {MODULE_NAME} ==="
input bool InpEnable{MODULE_NAME} = true;

class C{MODULE_NAME}
{{
private:
    bool m_init;
    string m_symbol;
    ENUM_TIMEFRAMES m_tf;

public:
    C{MODULE_NAME}() {{ m_init = false; }}
    ~C{MODULE_NAME}() {{ Deinit(); }}
    
    bool Init(string sym = "", ENUM_TIMEFRAMES tf = PERIOD_CURRENT)
    {{
        if(m_init) return true;
        m_symbol = (sym == "") ? _Symbol : sym;
        m_tf = (tf == PERIOD_CURRENT) ? Period() : tf;
        m_init = true;
        Print("[{MODULE_NAME}] Init OK");
        return true;
    }}
    
    void Deinit()
    {{
        if(!m_init) return;
        m_init = false;
    }}
    
    void OnTick()
    {{
        if(!m_init) return;
        // TODO: Logic
    }}
    
    bool IsInitialized() const {{ return m_init; }}
}};

// <dev:begin>
// Bloc développeur - jamais écrasé
// </dev:begin>

C{MODULE_NAME} *g_p{MODULE_NAME} = NULL;

bool Init{MODULE_NAME}(string sym = "", ENUM_TIMEFRAMES tf = PERIOD_CURRENT)
{{
    if(g_p{MODULE_NAME} != NULL) return g_p{MODULE_NAME}.IsInitialized();
    g_p{MODULE_NAME} = new C{MODULE_NAME}();
    if(g_p{MODULE_NAME} == NULL) return false;
    return g_p{MODULE_NAME}.Init(sym, tf);
}}

void Deinit{MODULE_NAME}()
{{
    if(g_p{MODULE_NAME} != NULL) {{ delete g_p{MODULE_NAME}; g_p{MODULE_NAME} = NULL; }}
}}

void OnTick{MODULE_NAME}()
{{
    if(g_p{MODULE_NAME} != NULL) g_p{MODULE_NAME}.OnTick();
}}

#endif
'''

# ════════════════════════════════════════════════════════════════════════════════
# UTILITIES
# ════════════════════════════════════════════════════════════════════════════════
def sha256(s): return hashlib.sha256(s.encode()).hexdigest()
def sha256_short(s): return sha256(s)[:16]
def sha256_file(p):
    h = hashlib.sha256()
    with open(p, 'rb') as f:
        for c in iter(lambda: f.read(8192), b''): h.update(c)
    return h.hexdigest()
def now_iso(): return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def session_id(): return datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
def run(cmd, cwd=None):
    try:
        r = subprocess.run(cmd, shell=True, cwd=cwd, capture_output=True, text=True, timeout=120)
        return r.returncode == 0, r.stdout + r.stderr
    except Exception as e: return False, str(e)

def find_base():
    for p in [Path("."), Path("/home/claude/IchiGrid-EA"), 
              Path(r"C:\Users\Enzo\Downloads\IchiGridEA\IchiGridEA")]:
        if (p / "IchiGridEA.mq5").exists(): return p.resolve()
    return Path(".").resolve()

# ════════════════════════════════════════════════════════════════════════════════
# WIRING - Câblage automatique
# ════════════════════════════════════════════════════════════════════════════════
def wire_modules(base):
    """Câble tous les modules orphelins dans IchiGridEA.mq5"""
    ea_file = base / "IchiGridEA.mq5"
    include_dir = base / "Include"
    
    content = ea_file.read_text(errors='ignore')
    current = set(re.findall(r'#include\s+"Include/([^"]+)"', content))
    all_mods = set(f.name for f in include_dir.glob("*.mqh"))
    orphans = all_mods - current
    
    if not orphans:
        print("✅ Tous les modules sont déjà câblés")
        return 0
    
    print(f"📦 {len(orphans)} modules à câbler...")
    
    # Ajouter includes
    lines = content.split('\n')
    last_inc = max(i for i, l in enumerate(lines) if l.strip().startswith('#include "Include/'))
    
    new_incs = [f'#include "Include/{m}"' for m in sorted(orphans)]
    lines = lines[:last_inc+1] + ["", "// AUTO-WIRED MODULES"] + new_incs + lines[last_inc+1:]
    
    ea_file.write_text('\n'.join(lines))
    print(f"✅ {len(orphans)} modules câblés")
    return len(orphans)

def add_wrappers(base):
    """Ajoute les fonctions wrapper manquantes"""
    include_dir = base / "Include"
    fixed = 0
    
    for mqh in include_dir.glob("*.mqh"):
        name = mqh.stem
        content = mqh.read_text(errors='ignore')
        
        if f"class C{name}" not in content:
            continue
        
        if f"Init{name}" in content and f"Deinit{name}" in content:
            continue
        
        wrapper = f'''
C{name} *g_p{name} = NULL;
bool Init{name}(string s="", ENUM_TIMEFRAMES t=PERIOD_CURRENT) {{
    if(g_p{name}!=NULL) return g_p{name}.IsInitialized();
    g_p{name} = new C{name}(); if(!g_p{name}) return false;
    return g_p{name}.Init(s,t);
}}
void Deinit{name}() {{ if(g_p{name}){{ delete g_p{name}; g_p{name}=NULL; }} }}
void OnTick{name}() {{ if(g_p{name}) g_p{name}.OnTick(); }}
'''
        # Ajouter avant #endif
        if "#endif" in content:
            content = content.replace("#endif", wrapper + "\n#endif", 1)
            mqh.write_text(content)
            fixed += 1
            print(f"  ✅ {name}")
    
    return fixed

# ════════════════════════════════════════════════════════════════════════════════
# TRACEABILITY
# ════════════════════════════════════════════════════════════════════════════════
def update_trace(base):
    """Met à jour PROJECT.json et HASHES.json"""
    data = {}; lines = 0; sections = set()
    
    for ext in ["*.mqh", "*.mq5"]:
        for f in base.rglob(ext):
            if ".git" in str(f) or ".checkpoints" in str(f): continue
            try:
                c = f.read_text(errors='ignore')
            except: continue
            rel = str(f.relative_to(base)).replace("\\", "/")
            l = len(c.splitlines()); lines += l
            m = re.search(r'@section\s*:\s*(S[\w\.\-]+)', c)
            if m: sections.add(m.group(1))
            data[rel] = {"sha256": sha256_file(f), "lines": l}
    
    ts = now_iso()
    with open(base / "HASHES.json", "w") as f:
        json.dump({"_generated": ts, "_files": len(data), "_lines": lines, "files": data}, f, indent=2)
    
    with open(base / "PROJECT.json", "w") as f:
        json.dump({
            "_updated": ts, "version": CONFIG["version"],
            "stats": {"files": len(data), "lines": lines, "sections": len(sections)},
            "sections": sorted(list(sections))
        }, f, indent=2)
    
    return {"files": len(data), "lines": lines, "sections": len(sections)}

# ════════════════════════════════════════════════════════════════════════════════
# CHECKPOINT
# ════════════════════════════════════════════════════════════════════════════════
def checkpoint(base, desc=""):
    cp_dir = base / ".checkpoints"; cp_dir.mkdir(exist_ok=True)
    cp_id = session_id(); cp_path = cp_dir / cp_id; cp_path.mkdir()
    
    for f in ["PROJECT.json", "HASHES.json", "IchiGridEA.mq5"]:
        if (base / f).exists(): shutil.copy2(base / f, cp_path / f)
    
    for d in ["Include", "Core"]:
        if (base / d).exists():
            (cp_path / d).mkdir(exist_ok=True)
            for m in (base / d).glob("*.mqh"): shutil.copy2(m, cp_path / d / m.name)
    
    meta = {"id": cp_id, "timestamp": now_iso(), "description": desc or f"Checkpoint {cp_id}"}
    (cp_path / "checkpoint.json").write_text(json.dumps(meta, indent=2))
    
    print(f"✅ Checkpoint: {cp_id}")
    return cp_id

# ════════════════════════════════════════════════════════════════════════════════
# DEPLOY
# ════════════════════════════════════════════════════════════════════════════════
def deploy(base):
    print("═" * 70)
    print("                    DÉPLOIEMENT")
    print("═" * 70)
    
    print("\n[1/4] Checkpoint...")
    checkpoint(base, "Pre-deploy")
    
    print("\n[2/4] Traçabilité...")
    s = update_trace(base)
    print(f"   {s['files']} fichiers | {s['lines']:,} lignes | {s['sections']} sections")
    
    print("\n[3/4] Git...")
    os.chdir(base)
    run("git add -A")
    msg = f"Deploy v{CONFIG['version']} - {s['files']} files, {s['sections']} sections"
    ok, out = run(f'git commit -m "{msg}"')
    if "nothing to commit" in out:
        print("   Rien à commiter")
    else:
        run(f"git push origin {CONFIG['branch']}")
        print("   ✅ Push OK")
    
    print("\n[4/4] Sync OVH...")
    key = os.path.expanduser(CONFIG["ovh_key"])
    cmd = f'ssh -i {key} -o StrictHostKeyChecking=no {CONFIG["ovh_user"]}@{CONFIG["ovh_ip"]} "cd {CONFIG["ovh_path"]} && git pull origin main"'
    ok, _ = run(cmd)
    print("   ✅ OVH sync OK" if ok else "   ⚠ OVH sync failed")
    
    print("\n" + "═" * 70)
    print("                    TERMINÉ")
    print("═" * 70)

# ════════════════════════════════════════════════════════════════════════════════
# GENERATE
# ════════════════════════════════════════════════════════════════════════════════
def generate(base, section, name, desc=""):
    ts = now_iso(); sess = session_id()
    
    code = TEMPLATE.replace("{MODULE_NAME}", name)
    code = code.replace("{MODULE_UPPER}", name.upper())
    code = code.replace("{SECTION_ID}", section)
    code = code.replace("{TIMESTAMP}", ts)
    code = code.replace("{SESSION}", sess)
    code = code.replace("{MODEL}", CONFIG["model"])
    code = code.replace("{DEPS}", "CommonDefinitions.mqh")
    code = code.replace("{DESC}", desc or f"Module {name}")
    code = code.replace("{CODE_HASH}", sha256_short(code))
    
    out = base / "Include" / f"{name}.mqh"
    out.write_text(code)
    print(f"✅ Généré: {out}")
    return out

# ════════════════════════════════════════════════════════════════════════════════
# STATUS
# ════════════════════════════════════════════════════════════════════════════════
def status(base):
    print("═" * 70)
    print("                    ICHIGRIDEA STATUS")
    print("═" * 70)
    
    # Compter
    mq5_files = list(base.rglob("*.mq5")) + list(base.rglob("*.mqh"))
    mq5_files = [f for f in mq5_files if ".git" not in str(f) and ".checkpoints" not in str(f)]
    
    total_lines = sum(len(f.read_text(errors='ignore').splitlines()) for f in mq5_files)
    
    # Modules
    include_dir = base / "Include"
    modules = list(include_dir.glob("*.mqh")) if include_dir.exists() else []
    
    # Câblés vs orphelins
    ea_content = (base / "IchiGridEA.mq5").read_text(errors='ignore')
    wired = set(re.findall(r'#include\s+"Include/([^"]+)"', ea_content))
    orphans = set(m.name for m in modules) - wired
    
    print(f"\n📊 FICHIERS")
    print(f"   Total: {len(mq5_files)} fichiers MQL5")
    print(f"   Lignes: {total_lines:,}")
    
    print(f"\n📦 MODULES Include/")
    print(f"   Total: {len(modules)}")
    print(f"   Câblés: {len(wired)}")
    print(f"   Orphelins: {len(orphans)}")
    
    if orphans:
        print(f"\n   ⚠️ Non câblés:")
        for o in sorted(orphans)[:5]:
            print(f"      - {o}")
        if len(orphans) > 5:
            print(f"      ... +{len(orphans)-5} autres")
    
    # Checkpoints
    cp_dir = base / ".checkpoints"
    if cp_dir.exists():
        cps = sorted(cp_dir.glob("*"), reverse=True)
        print(f"\n💾 CHECKPOINTS: {len(cps)}")

# ════════════════════════════════════════════════════════════════════════════════
# MAIN
# ════════════════════════════════════════════════════════════════════════════════
def main():
    base = find_base()
    print(f"📁 {base}\n")
    
    if len(sys.argv) < 2:
        # Mode autonome complet
        print("═" * 70)
        print("          MODE AUTONOME COMPLET")
        print("═" * 70)
        
        print("\n[1/5] Analyse...")
        status(base)
        
        print("\n[2/5] Câblage modules...")
        wire_modules(base)
        
        print("\n[3/5] Ajout wrappers...")
        add_wrappers(base)
        
        print("\n[4/5] Traçabilité...")
        update_trace(base)
        
        print("\n[5/5] Déploiement...")
        deploy(base)
        return
    
    action = sys.argv[1].lower()
    
    if action == "status":
        status(base)
    elif action == "wire":
        wire_modules(base)
        add_wrappers(base)
    elif action == "fix":
        add_wrappers(base)
        update_trace(base)
    elif action == "deploy":
        deploy(base)
    elif action == "checkpoint":
        desc = " ".join(sys.argv[2:]) if len(sys.argv) > 2 else ""
        checkpoint(base, desc)
    elif action == "generate":
        if len(sys.argv) < 4:
            print("Usage: generate <section> <nom> [desc]")
            return
        desc = " ".join(sys.argv[4:]) if len(sys.argv) > 4 else ""
        generate(base, sys.argv[2], sys.argv[3], desc)
    else:
        print(__doc__)

if __name__ == "__main__":
    main()
