import os
import sys
import json
import time
import urllib.request
import urllib.error
import pathlib
from datetime import datetime

# ROOT now resolved via frozen_path (PyInstaller-safe)
from frozen_path import APP_ROOT as ROOT
sys.path.insert(0, str(ROOT))

from config_manager import get_app_dir as _get_app_dir
_DATA_ROOT = _get_app_dir() if getattr(sys, 'frozen', False) else ROOT

from src.agent_role_router import ROLE_LOCAL_PATROL, call_llm

LOG_FILE = _DATA_ROOT / "logs" / "gold_trader.daemon.log"
HEALING_HISTORY = _DATA_ROOT / "data" / "healing_history.json"
CURSOR_FILE = _DATA_ROOT / "data" / "self_healer_cursor.txt"

def extract_json_block(text):
    """Extract JSON from LLM output (delegates to shared utility)."""
    try:
        from src.json_utils import extract_json_block as _extract
        return _extract(text)
    except Exception:
        return None

def read_new_errors():
    if not LOG_FILE.exists():
        return ""
    
    last_size = 0
    if CURSOR_FILE.exists():
        try:
            last_size = int(CURSOR_FILE.read_text(encoding="utf-8").strip())
        except Exception:
            pass

    current_size = LOG_FILE.stat().st_size
    if current_size < last_size: # Log rotated
        last_size = 0

    with open(LOG_FILE, "r", encoding="utf-8", errors="ignore") as f:
        f.seek(last_size)
        new_content = f.read()
        CURSOR_FILE.write_text(str(current_size))

    error_lines = []
    lines = new_content.splitlines()
    for i, line in enumerate(lines):
        if "[ERROR]" in line or "[CRITICAL]" in line or "Traceback" in line or "Exception" in line:
            start = max(0, i - 2)
            end = min(len(lines), i + 5)
            error_lines.extend(lines[start:end])
    
    if not error_lines:
        return ""
    return "\n".join(list(dict.fromkeys(error_lines)))

def execute_action(action):
    print(f"[{datetime.now()}] Executing Healing Action: {action}")
    if action == "RESTART_DAEMON":
        os.system(
            f"pkill -f '{ROOT}/src/pipeline.py'; sleep 2; "
            f"nohup python3 {ROOT}/src/pipeline.py --source ea_feed --runtime-intent live_trade "
            f"--execution-mode ea_bridge --symbol XAUUSD --timeframe M5 "
            f"--input-path {ROOT}/data/inbox/request.json --output-path {ROOT}/data/outbox/signal.json "
            f"--execution-report-path {ROOT}/data/inbox/execution_report.json --poll-seconds 2 "
            f"--enable-incident-autofix true >> {LOG_FILE} 2>&1 &"
        )
    elif action == "CLEAR_CONFIG_CACHE":
        cfg = _DATA_ROOT / "app_config.json"
        if cfg.exists():
            cfg.write_text("{}")
        os.system(
            f"pkill -f '{ROOT}/src/pipeline.py'; sleep 2; "
            f"nohup python3 {ROOT}/src/pipeline.py --source ea_feed --runtime-intent live_trade "
            f"--execution-mode ea_bridge --symbol XAUUSD --timeframe M5 "
            f"--input-path {ROOT}/data/inbox/request.json --output-path {ROOT}/data/outbox/signal.json "
            f"--execution-report-path {ROOT}/data/inbox/execution_report.json --poll-seconds 2 "
            f"--enable-incident-autofix true >> {LOG_FILE} 2>&1 &"
        )
    elif action == "CLEAR_DB_LOCKS":
        db = _DATA_ROOT / "data" / "bridge" / "state" / "agent_state.db"
        if db.exists():
            os.system(f"sqlite3 {db} 'PRAGMA wal_checkpoint(TRUNCATE);'")
        os.system(
            f"pkill -f '{ROOT}/src/pipeline.py'; sleep 2; "
            f"nohup python3 {ROOT}/src/pipeline.py --source ea_feed --runtime-intent live_trade "
            f"--execution-mode ea_bridge --symbol XAUUSD --timeframe M5 "
            f"--input-path {ROOT}/data/inbox/request.json --output-path {ROOT}/data/outbox/signal.json "
            f"--execution-report-path {ROOT}/data/inbox/execution_report.json --poll-seconds 2 "
            f"--enable-incident-autofix true >> {LOG_FILE} 2>&1 &"
        )
    else:
        print(f"Unknown or NOTHING action: {action}")

def ask_local_llm(prompt: str) -> str:
    """Try to use local oMLX (OpenClaw MLX) model for fast, free high-frequency inference."""
    try:
        # 兼容 OpenAI 的 API 格式，oMLX 运行在本地 8002 端口
        req = urllib.request.Request(
            "http://127.0.0.1:8002/v1/chat/completions",
            data=json.dumps({
                "model": "Qwen3.5-4B-MLX-4bit", # 绑定到界面的兜底模型
                "messages": [{"role": "user", "content": prompt}],
                "temperature": 0.1
            }).encode("utf-8"),
            headers={"Content-Type": "application/json", "Authorization": "Bearer 786540"}
        )
        with urllib.request.urlopen(req, timeout=15) as response:
            res_data = json.loads(response.read().decode("utf-8"))
            if "choices" in res_data and len(res_data["choices"]) > 0:
                return res_data["choices"][0]["message"]["content"]
            return ""
    except Exception as e:
        print(f"Local oMLX LLM failed (is oMLX server running on port 8002?): {e}")
        return ""

def run_self_healer():
    print(f"[{datetime.now()}] Running Self-Healing Engine...")
    error_context = read_new_errors()
    
    if not error_context:
        print("System is healthy. No new errors detected.")
        try:
            from src.swarm.blackboard import global_blackboard
            global_blackboard.update("Local_Patrol", "status", "HEALTHY")
            global_blackboard.update("Local_Patrol", "last_updated", time.time())
        except Exception:
            pass
        return

    print("Errors detected! Calling LLM Agent for diagnosis...")
    
    prompt = f"""
你是黄金算法交易系统的自愈引擎（Auto-Repair）。
请阅读以下近期错误日志，诊断根因，并给出标准修复动作。

可用动作：
1. "RESTART_DAEMON" — 进程卡死/内存泄漏/管道僵死 或 连续多次同类异常
2. "CLEAR_CONFIG_CACHE" — 错误提到 "terminal_status.json not found" / MT5路径故障 / config 解析失败
3. "CLEAR_DB_LOCKS" — 错误为 "OperationalError: database is locked" 或 SQLite 锁竞争
4. "NOTHING" — 正常业务拦截（如 "RiskManager BLOCKED"/"Agent宏观定调"/"COUNTERTREND"）或无害警告

判断规则：
- Traceback + 进程异常 → RESTART_DAEMON
- MT5/路径/config 相关 → CLEAR_CONFIG_CACHE
- 数据库锁 → CLEAR_DB_LOCKS
- 业务拦截/风控驳回/正常回调 → NOTHING

近期错误日志：
{error_context[:2000]}

请严格输出此 JSON 格式（不要包含其他文字）：
{{{{
  "rca": "根因分析（一句话中文）",
  "action": "RESTART_DAEMON|CLEAR_CONFIG_CACHE|CLEAR_DB_LOCKS|NOTHING"
}}}}
"""

    data = None
    
    # 1. 优先尝试本地 Ollama 大模型 (0 token cost, 高频可用)
    local_output = ask_local_llm(prompt)
    if local_output:
        data = extract_json_block(local_output)
    
    # 2. 如果本地大模型失败或未开启，降级 (Fallback) 到云端
    if not data:
        print("Falling back to Cloud LLM...")
        try:
            result = call_llm(ROLE_LOCAL_PATROL, prompt, temperature=0.1, max_tokens=500, timeout=60)
            if result:
                data = extract_json_block(result)
            else:
                print(f"Cloud LLM fallback failed: empty result")
        except Exception as e:
            print(f"Cloud LLM fallback failed: {e}")

    if data and "action" in data:
        action = data["action"]
        rca = data.get("rca", "Unknown")
        print(f"Diagnosis: {rca}")
        print(f"Prescribed Action: {action}")
        
        if action != "NOTHING":
            try:
                from src.swarm.blackboard import global_blackboard
                global_blackboard.update("Local_Patrol", "rca", rca)
                global_blackboard.update("Local_Patrol", "action", action)
                global_blackboard.update("Local_Patrol", "status", "HEALING")
                global_blackboard.update("Local_Patrol", "last_updated", time.time())
                global_blackboard.add_log("Local_Patrol", "HEAL", f"[ALERT] 触发自愈: {rca} -> 执行: {action}")
            except Exception:
                pass

            execute_action(action)
            
            history = []
            if HEALING_HISTORY.exists():
                try:
                    history = json.loads(HEALING_HISTORY.read_text(encoding="utf-8"))
                except Exception:
                    pass
            history.append({
                "timestamp": time.time(),
                "date": datetime.now().isoformat(),
                "rca": rca,
                "action": action
            })
            HEALING_HISTORY.parent.mkdir(parents=True, exist_ok=True)
            HEALING_HISTORY.write_text(json.dumps(history[-50:], indent=2))
    else:
        print("Invalid JSON returned by LLM.")
            
if __name__ == "__main__":
    run_self_healer()
