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

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 pathlib import Path; import os; _apd = Path(os.environ.get("APPDATA","")); _cmn = _apd / "MetaQuotes" / "Terminal" / "Common" / "Files"; BRIDGE_TO_EA = _cmn / "gold_autotrade" / "bridge" / "to_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",
    "原油库存",
)

# ── 各国专属 BLOCK 级关键词 ──────────────────────────────
MACRO_EU_BLOCK_KEYWORDS = (
    "ecb",
    "拉加德",
    "lagarde",
    "欧央行",
    "欧洲央行",
    "再融资利率",
    "存款便利利率",
    "边际贷款便利利率",
)

MACRO_GB_BLOCK_KEYWORDS = (
    "boe",
    "英央行",
    "英格兰银行",
    "贝利",
    "bailey",
    "货币政策报告",
    "mpc",
    "官方银行利率",
)

MACRO_JP_BLOCK_KEYWORDS = (
    "boj",
    "日央行",
    "日本央行",
    "植田",
    "ueda",
    "黑田",
    "kuroda",
    "短观",
    "tankan",
)

MACRO_CN_BLOCK_KEYWORDS = (
    "pboc",
    "人民银行",
    "央行",
    "中国人民银行",
    "贷款市场报价利率",
    "lpr",
    "中期借贷便利",
    "mlf",
    "存款准备金率",
    "rrr",
    "社会融资规模",
    "新增人民币贷款",
)

# ── 各国专属 ALERT 级关键词 ─────────────────────────────
MACRO_EU_ALERT_KEYWORDS = (
    "ifo",
    "zew",
    "德国",
    "欧元区",
    "调和cpi",
    "hicp",
    "sentix",
)

MACRO_GB_ALERT_KEYWORDS = (
    "英国",
    "uk",
    "cbi",
    "工业订单",
)

MACRO_JP_ALERT_KEYWORDS = (
    "日本",
    "东京cpi",
    "全国cpi",
    "核心cpi",
    "第三产业",
    "家庭支出",
    "零售",
)

MACRO_CN_ALERT_KEYWORDS = (
    "中国",
    "china",
    "财新",
    "caixin",
    "官方pmi",
    "nbs",
    "贸易帐",
    "进出口",
    "规模以上工业",
    "社会消费品零售",
)

# ── 多国家日历请求 ──────────────────────────────────────
CALENDAR_COUNTRIES = ["US", "EU", "GB", "JP", "CN"]

def _get_country_block_keywords(country: str):
    mapping = {
        "EU": MACRO_EU_BLOCK_KEYWORDS,
        "GB": MACRO_GB_BLOCK_KEYWORDS,
        "JP": MACRO_JP_BLOCK_KEYWORDS,
        "CN": MACRO_CN_BLOCK_KEYWORDS,
    }
    return mapping.get(country, ())

def _get_country_alert_keywords(country: str):
    mapping = {
        "EU": MACRO_EU_ALERT_KEYWORDS,
        "GB": MACRO_GB_ALERT_KEYWORDS,
        "JP": MACRO_JP_ALERT_KEYWORDS,
        "CN": MACRO_CN_ALERT_KEYWORDS,
    }
    return mapping.get(country, ())


def _classify_macro_event_for_country(name: str, country: str) -> str:
    """结合国家上下文对事件进行分级"""
    normalized = _normalize_event_name(name)
    if not normalized:
        return "IGNORE"
    if _contains_any(normalized, MACRO_DENY_KEYWORDS):
        return "IGNORE"
    # 该国专属 BLOCK
    if _contains_any(normalized, _get_country_block_keywords(country)):
        return "BLOCK"
    # 该国专属 ALERT
    if _contains_any(normalized, _get_country_alert_keywords(country)):
        return "ALERT"
    # 通用规则
    if _contains_any(normalized, MACRO_BLOCK_KEYWORDS):
        return "BLOCK"
    if _contains_any(normalized, MACRO_ALERT_KEYWORDS):
        return "ALERT"
    return "IGNORE"


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_single_country_calendar(country: str) -> list:
    """向 EA 请求单个国家的日历数据，返回事件列表"""
    req_id = f"calendar_{country.lower()}_{int(time.time())}"
    req = {
        "signal_id": req_id,
        "signal": {
            "action": "GET_CALENDAR",
            "country": country
        }
    }

    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))

    report_file = BRIDGE_FROM_EA / f"{req_id}.report.json"
    waited = 0
    ea_response = None
    while waited < 10:
        if report_file.exists():
            try:
                try:
                    with open(report_file, "r", encoding="utf-8") as f:
                        raw_text = f.read()
                        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:
                print(f"Error reading EA report for {country}: {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"[WARN]️  Failed to get calendar for {country}")
        return []

    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 for {country}: {e}")
        return []

    return cal_data.get("events", [])


def fetch_mt5_calendar():
    """
    通过向 EA 发送 GET_CALENDAR 指令，获取 MT5 内置的多国权威结构化财经日历。
    覆盖 US/EU/GB/JP/CN，零外部网络依赖，无被反爬/封禁风险。
    """
    now = datetime.now()
    all_events = []
    country_stats = {}

    for country in CALENDAR_COUNTRIES:
        try:
            events = _fetch_single_country_calendar(country)
            country_stats[country] = len(events)
            for ev in events:
                ev["_country"] = country
            all_events.extend(events)
        except Exception as e:
            print(f"Error fetching {country} calendar: {e}")
            country_stats[country] = 0

    print(f"[{datetime.now().strftime('%H:%M:%S')}] Calendar fetch: {country_stats}")

    # 处理所有事件
    processed_events = []
    ignored_events = []
    block_count = 0
    alert_count = 0

    for ev in all_events:
        try:
            event_name = ev.get("event", "")
            country = ev.get("_country", "US")
            tier = _classify_macro_event_for_country(event_name, country)
            if tier == "IGNORE":
                if event_name:
                    ignored_events.append(f"[{country}] {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"),
                "time_full": ev_time.strftime("%Y-%m-%d %H:%M"),
                "impact": "HIGH",
                "currency": country,
                "country": country,
                "tier": tier,
                "block_start": block_start.strftime("%H:%M"),
                "block_end": block_end.strftime("%H:%M"),
                "description": f"MT5 {country} Impact Event - Auto Blocked" if tier == "BLOCK" else f"MT5 {country} Impact Event - Alert Only"
            })
        except Exception as e:
            print(f"Error parsing event {ev.get('time')} ({ev.get('event')}): {e}")

    # 按时间排序
    processed_events.sort(key=lambda x: x["time"])

    final_data = {
        "generated_at": now.isoformat(),
        "countries_covered": list(CALENDAR_COUNTRIES),
        "events": processed_events,
        "block_event_count": block_count,
        "alert_event_count": alert_count,
        "ignored_event_count": len(ignored_events),
        "ignored_event_samples": ignored_events[:15],
    }

    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"[OK] Macro schedule generated from MT5 Calendar ({', '.join(CALENDAR_COUNTRIES)}). 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)
            
        # 检查文件是否过期（比如是昨天的文件）
        # 使用 UTC 避免与 MT5 服务器时间错配
        tz8 = timezone(timedelta(hours=8))
        gen_time = datetime.fromisoformat(data.get("generated_at", "2000-01-01T00:00:00")).replace(tzinfo=tz8)
        now_utc = datetime.now(timezone.utc)
        if (now_utc - gen_time).total_seconds() > 86400:
            return {"tier": "NONE", "reason": "", "event": None}

        now_str = now_utc.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()
