import json
import time
import os
import re
from datetime import datetime, timedelta

MACRO_FILE = os.path.join(os.path.dirname(__file__), "../logs/macro_block_schedule.json")
# 导入 EA 桥接目录配置
import sys
import pathlib
ROOT = pathlib.Path(__file__).parent.parent.absolute()
sys.path.insert(0, str(ROOT))
from bridge.gold_trader import BRIDGE_TO_EA, BRIDGE_FROM_EA

MACRO_BLOCK_KEYWORDS = (
    "非农",
    "cpi",
    "核心cpi",
    "ppi",
    "pce",
    "核心pce",
    "利率决议",
    "联邦基金",
    "fomc",
    "美联储",
    "会议纪要",
    "鲍威尔",
    "杰克逊霍尔",
)

MACRO_ALERT_KEYWORDS = (
    "adp",
    "失业",
    "初领失业金",
    "初请失业金",
    "零售销售",
    "核心零售销售",
    "ism",
    "制造业pmi",
    "服务业pmi",
    "采购经理人指数",
)

MACRO_GDP_KEYWORDS = (
    "gdp季率",
    "gdp年化",
    "实际gdp",
    "国内生产总值季率",
    "国内生产总值年化",
    "国内生产总值",
)

MACRO_RETAIL_KEYWORDS = (
    "零售销售",
    "核心零售销售",
)

MACRO_DENY_KEYWORDS = (
    "gdp销售",
    "销售季率",
    "销售额",
    "批发销售",
    "成品房",
    "新屋销售",
    "成屋销售",
    "房屋销售",
    "住房",
    "房屋",
    "房产",
    "房价",
    "营建许可",
    "新屋开工",
    "抵押贷款",
    "耐用品",
    "工厂订单",
    "30年期国债拍卖",
    "10年期国债拍卖",
    "国债拍卖",
    "eia",
    "原油库存",
)


def _normalize_event_name(name: str) -> str:
    return re.sub(r"[^a-z0-9\u4e00-\u9fff]+", "", str(name or "").strip().lower())


def _contains_any(normalized: str, keywords) -> bool:
    return any(keyword in normalized for keyword in keywords)


def _classify_macro_event(name: str) -> str:
    normalized = _normalize_event_name(name)
    if not normalized:
        return "IGNORE"
    if _contains_any(normalized, MACRO_DENY_KEYWORDS):
        return "IGNORE"
    if "gdp" in normalized or "国内生产总值" in normalized:
        return "BLOCK" if _contains_any(normalized, MACRO_GDP_KEYWORDS) else "IGNORE"
    if "销售" in normalized and not _contains_any(normalized, MACRO_RETAIL_KEYWORDS):
        return "IGNORE"
    if _contains_any(normalized, MACRO_BLOCK_KEYWORDS):
        return "BLOCK"
    if _contains_any(normalized, MACRO_ALERT_KEYWORDS):
        return "ALERT"
    return "IGNORE"


def _is_relevant_macro_event(name: str) -> bool:
    return _classify_macro_event(name) != "IGNORE"


def _resolve_event_window_minutes(tier: str) -> tuple[int, int]:
    if tier == "ALERT":
        return 3, 5
    return 5, 10

def fetch_mt5_calendar():
    """
    通过向 EA 发送 GET_CALENDAR 指令，获取 MT5 内置的权威结构化财经日历。
    零外部网络依赖，无被反爬/封禁风险。
    """
    req_id = f"calendar_{int(time.time())}"
    req = {
        "signal_id": req_id,
        "signal": {
            "action": "GET_CALENDAR",
            "country": "US"
        }
    }
    
    # 1. 写入请求文件
    BRIDGE_TO_EA.mkdir(parents=True, exist_ok=True)
    req_file = BRIDGE_TO_EA / f"{req_id}.request.json"
    req_file.write_text(json.dumps(req, indent=2))
    print(f"[{datetime.now().strftime('%H:%M:%S')}] Sent GET_CALENDAR request to EA...")

    # 2. 轮询等待 EA 响应（最多等 10 秒）
    report_file = BRIDGE_FROM_EA / f"{req_id}.report.json"
    waited = 0
    ea_response = None
    while waited < 10:
        if report_file.exists():
            try:
                # 尝试用 utf-8 读取，如果失败则回退到 gbk 或 ansi（因为 MT5 的 FILE_ANSI 在中文系统下可能是 GBK）
                try:
                    with open(report_file, "r", encoding="utf-8") as f:
                        raw_text = f.read()
                        # MT5 生成的 JSON 中可能带有未转义的特殊字符（比如日历事件里的双引号），
                        # 如果用 json.load 直接解析可能会报错，我们这里尝试处理一下或者忽略。
                        # 但外层的大结构应该是对的
                        ea_response = json.loads(raw_text, strict=False)
                except UnicodeDecodeError:
                    with open(report_file, "r", encoding="gbk") as f:
                        raw_text = f.read()
                        ea_response = json.loads(raw_text, strict=False)
                        
                report_file.unlink(missing_ok=True)
                break
            except Exception as e:
                # 打印出错的原始文本，方便我们排查 MQL5 生成的 JSON 哪里少了逗号
                print(f"Error reading EA report: {e}")
                try:
                    print(f"Raw report text: {raw_text}")
                except:
                    pass
                # 如果一直解析失败，不要删文件，方便后面手工查看
                break
        time.sleep(1)
        waited += 1

    if not ea_response or ea_response.get("status") != "SUCCESS":
        print(f"❌ Failed to get calendar from EA. Response: {ea_response}")
        return None

    # 3. 解析 EA 返回的 JSON 字符串（注意 EA 把 JSON 塞在了 retcode 字段里，并且里面的双引号被替换为了单引号）
    raw_cal_json = ea_response.get("retcode", "{}")
    raw_cal_json = raw_cal_json.replace("'", "\"")  # 还原回双引号
    try:
        cal_data = json.loads(raw_cal_json)
    except Exception as e:
        print(f"❌ Failed to parse calendar JSON from EA: {e}")
        print(f"Raw string: {raw_cal_json}")
        return None

    # 4. 转换时间格式并生成封锁时间表
    now = datetime.now()
    processed_events = []
    ignored_events = []
    block_count = 0
    alert_count = 0
    
    for ev in cal_data.get("events", []):
        try:
            event_name = ev.get("event", "")
            tier = _classify_macro_event(event_name)
            if tier == "IGNORE":
                if event_name:
                    ignored_events.append(event_name)
                continue
            # MT5 返回的时间格式如 "2026.03.24 20:30:00"
            ev_time = datetime.strptime(ev["time"], "%Y.%m.%d %H:%M:%S")
            minutes_before, minutes_after = _resolve_event_window_minutes(tier)
            block_start = ev_time - timedelta(minutes=minutes_before)
            block_end = ev_time + timedelta(minutes=minutes_after)
            if tier == "BLOCK":
                block_count += 1
            elif tier == "ALERT":
                alert_count += 1
            
            processed_events.append({
                "event": event_name,
                "time": ev_time.strftime("%H:%M"),
                "impact": "HIGH",
                "currency": "USD",
                "tier": tier,
                "block_start": block_start.strftime("%H:%M"),
                "block_end": block_end.strftime("%H:%M"),
                "description": "MT5 High Impact Event - Auto Blocked" if tier == "BLOCK" else "MT5 Medium Impact Event - Alert Only"
            })
        except Exception as e:
            print(f"Error parsing event time {ev.get('time')}: {e}")

    final_data = {
        "generated_at": now.isoformat(),
        "events": processed_events,
        "block_event_count": block_count,
        "alert_event_count": alert_count,
        "ignored_event_count": len(ignored_events),
        "ignored_event_samples": ignored_events[:10],
    }
    
    os.makedirs(os.path.dirname(MACRO_FILE), exist_ok=True)
    with open(MACRO_FILE, "w", encoding="utf-8") as f:
        json.dump(final_data, f, ensure_ascii=False, indent=2)
    
    print(f"✅ Macro schedule generated successfully from MT5 Calendar. block={block_count} alert={alert_count} ignored={len(ignored_events)}")
    return final_data

def get_macro_event_signal():
    if not os.path.exists(MACRO_FILE):
        return {"tier": "NONE", "reason": "", "event": None}
        
    try:
        with open(MACRO_FILE, "r", encoding="utf-8") as f:
            data = json.load(f)
            
        # 检查文件是否过期（比如是昨天的文件）
        gen_time = datetime.fromisoformat(data.get("generated_at", "2000-01-01T00:00:00"))
        if (datetime.now() - gen_time).days > 0:
            return {"tier": "NONE", "reason": "", "event": None}
            
        now_str = datetime.now().strftime("%H:%M")
        active_alert = None
        
        for ev in data.get("events", []):
            tier = ev.get("tier") or _classify_macro_event(ev.get("event", ""))
            if tier == "IGNORE":
                continue
            if ev["block_start"] <= now_str <= ev["block_end"]:
                reason = f"{ev['event']} ({ev['block_start']} - {ev['block_end']})"
                if tier == "BLOCK":
                    return {"tier": "BLOCK", "reason": reason, "event": ev}
                if tier == "ALERT" and active_alert is None:
                    active_alert = {"tier": "ALERT", "reason": reason, "event": ev}
        return active_alert or {"tier": "NONE", "reason": "", "event": None}
    except Exception as e:
        print(f"Error reading macro file: {e}")
        return {"tier": "NONE", "reason": "", "event": None}


def is_macro_blocked():
    signal = get_macro_event_signal()
    return signal.get("tier") == "BLOCK", signal.get("reason", "")

if __name__ == "__main__":
    fetch_mt5_calendar()
