"""
Startup Gate — 启动前自检门禁。

在任何交易进程启动前运行，检查：
1. 状态文件存在且可读（agent_state, execution_state, position_lifecycle, agent_decision_contract）
2. 代码一致性（release 目录 vs 源码目录，防止漂移）
3. 关键契约不变量（macro direction → allowed_side 一致）
4. 桥接路径存在
5. 商业化门禁：许可证有效性
6. 商业化门禁：风险披露已接受

返回: {"ok": True/False, "checks": [...], "failures": [...]}
所有不通过即标红 FAIL，不允许 "看起来还行"。

Gate 5 和 Gate 6 默认 SKIP（向后兼容），设置环境变量
  GAT_COMMERCIAL_MODE=1  启用商业化门禁
"""

from __future__ import annotations

import json
import hashlib
import os
import sys
from pathlib import Path
from typing import Any, Optional


def _resolve_project_root() -> Path:
    return Path(__file__).resolve().parents[1]


def _resolve_app_dir() -> Optional[Path]:
    env = os.environ.get("LOBSTERQUANT_APP_DIR", "").strip()
    if env:
        return Path(env)
    try:
        import config_manager
        d = config_manager.get_app_dir()
        return Path(d) if d else None
    except Exception:
        return None


def _resolve_release_root(app_dir: Optional[Path]) -> Optional[Path]:
    """Auto-detect the active managed_app release directory."""
    if app_dir is None:
        return None
    managed = app_dir / "managed_app" / "releases"
    if not managed.exists():
        return None
    # Look for the highest version release that's not staging
    best = None
    best_ver = (0, 0, 0)
    for d in managed.iterdir():
        if not d.is_dir() or d.name.startswith("."):
            continue
        try:
            parts = tuple(int(x) for x in d.name.split("."))
            if len(parts) >= 2 and parts > best_ver:
                best_ver = parts
                best = d
        except ValueError:
            pass
    return best


def _check_file(path: Path, label: str) -> dict:
    if not path.exists():
        return {"check": label, "status": "FAIL", "reason": f"file missing: {path}"}
    try:
        with open(path) as f:
            json.load(f)
        return {"check": label, "status": "PASS"}
    except json.JSONDecodeError as e:
        return {"check": label, "status": "FAIL", "reason": f"invalid JSON: {e}"}
    except Exception as e:
        return {"check": label, "status": "FAIL", "reason": str(e)}


def _check_bridge_dirs(project_root: Path) -> list[dict]:
    """Check bridge to_ea/from_ea directories via ea_bridge_paths."""
    results = []
    try:
        try:
            from src.ea_bridge_paths import resolve_bridge_layout
        except ImportError:
            sys.path.insert(0, str(project_root))
            from src.ea_bridge_paths import resolve_bridge_layout  # type: ignore[no-redef]
        layout = resolve_bridge_layout(project_root)
        for key in ["to_ea", "from_ea"]:
            d = layout.get(key)
            if d and d.exists() and d.is_dir():
                results.append({"check": f"bridge/{key}", "status": "PASS", "detail": str(d)})
            else:
                results.append({"check": f"bridge/{key}", "status": "FAIL",
                                "reason": f"directory missing or inaccessible: {d}"})
    except Exception as e:
        results.append({"check": "bridge/layout", "status": "FAIL", "reason": str(e)})
    return results


def _check_code_integrity(project_root: Path, release_root: Optional[Path]) -> list[dict]:
    """Check that release directory code matches source (no drift)."""
    if release_root is None:
        return [{"check": "code_integrity", "status": "SKIP", "reason": "no release_root available"}]

    critical_files = [
        "bridge/gold_trader.py",
        "src/pipeline.py",
        "bridge/commander_client.py",
        "src/mt5_execution.py",
        "bridge/exit_optimizer.py",
        "src/position_lifecycle.py",
    ]
    results = []
    for f in critical_files:
        src = project_root / f
        rel = release_root / f
        if not src.exists():
            results.append({"check": f"code_integrity/{f}", "status": "SKIP", "reason": "source missing"})
            continue
        if not rel.exists():
            results.append({"check": f"code_integrity/{f}", "status": "FAIL", "reason": f"release file missing: {rel}"})
            continue
        try:
            src_hash = hashlib.md5(src.read_bytes()).hexdigest()
            rel_hash = hashlib.md5(rel.read_bytes()).hexdigest()
            if src_hash == rel_hash:
                results.append({"check": f"code_integrity/{f}", "status": "PASS"})
            else:
                results.append({"check": f"code_integrity/{f}", "status": "FAIL",
                                "reason": f"hash mismatch src={src_hash[:8]} rel={rel_hash[:8]}"})
        except Exception as e:
            results.append({"check": f"code_integrity/{f}", "status": "FAIL", "reason": str(e)})
    return results


def _check_contract_invariants(app_dir: Path) -> list[dict]:
    """Verify key invariants: macro direction → allowed_side consistency."""
    results = []
    contract_path = app_dir / "data" / "agent_decision_contract.json"
    if not contract_path.exists():
        return [{"check": "contract_invariant", "status": "SKIP", "reason": "contract file missing"}]

    try:
        with open(contract_path) as f:
            contract = json.load(f)
    except Exception as e:
        return [{"check": "contract_invariant", "status": "FAIL", "reason": str(e)}]

    macro = contract.get("macro", {}) if isinstance(contract.get("macro", {}), dict) else {}
    direction = str(macro.get("direction", "NEUTRAL") or "NEUTRAL").upper()
    degraded = bool(macro.get("degraded", True))
    confidence = float(macro.get("confidence", 0.0) or 0.0)

    # Invariant 1: direction must be LONG/SHORT/NEUTRAL
    if direction in ("LONG", "SHORT", "NEUTRAL"):
        results.append({"check": "invariant/macro_direction_valid", "status": "PASS"})
    else:
        results.append({"check": "invariant/macro_direction_valid", "status": "FAIL",
                        "reason": f"invalid direction: {direction}"})

    # Invariant 2: confidence range [0, 1]
    if 0.0 <= confidence <= 1.0:
        results.append({"check": "invariant/confidence_range", "status": "PASS"})
    else:
        results.append({"check": "invariant/confidence_range", "status": "FAIL",
                        "reason": f"confidence out of range: {confidence}"})

    # Invariant 3: if FRESH and directional, binding must match
    binding = str(macro.get("binding_direction", "NEUTRAL") or "NEUTRAL").upper()
    allowed_side = str(macro.get("allowed_side", "BOTH") or "BOTH").upper()
    if not degraded and binding in ("LONG", "SHORT"):
        expected_allowed = "BUY" if binding == "LONG" else "SELL"
        if allowed_side == expected_allowed or allowed_side == "BOTH":
            results.append({"check": "invariant/binding_allowed_side", "status": "PASS"})
        else:
            results.append({"check": "invariant/binding_allowed_side", "status": "FAIL",
                            "reason": f"binding={binding} but allowed_side={allowed_side} (expected {expected_allowed})"})
    else:
        results.append({"check": "invariant/binding_allowed_side", "status": "PASS",
                        "detail": f"degraded={degraded} binding={binding}"})

    return results


def run_startup_gate(
    project_root: Optional[Path] = None,
    release_root: Optional[Path] = None,
    app_dir: Optional[Path] = None,
) -> dict[str, Any]:
    """Run all startup checks. Returns {"ok": bool, "checks": [...], "failures": [...]}."""
    if project_root is None:
        project_root = _resolve_project_root()
    if app_dir is None:
        app_dir = _resolve_app_dir()
    if app_dir is None:
        return {"ok": False, "checks": [], "failures": ["APP_DIR not resolved"]}

    all_checks: list[dict] = []

    # Gate 1: State files
    state_files = [
        (app_dir / "data" / "bridge" / "state" / "agent_state.json", "agent_state.json"),
        (app_dir / "data" / "execution_state.json", "execution_state.json"),
        (app_dir / "data" / "position_lifecycle.json", "position_lifecycle.json"),
        (app_dir / "data" / "agent_decision_contract.json", "agent_decision_contract.json"),
    ]
    for path, label in state_files:
        all_checks.append(_check_file(path, label))

    # Gate 2: Bridge directories
    all_checks.extend(_check_bridge_dirs(project_root))

    # Gate 3: Code integrity (source vs release)
    if release_root is None:
        release_root = _resolve_release_root(app_dir)
    all_checks.extend(_check_code_integrity(project_root, release_root))

    # Gate 4: Contract invariants
    all_checks.extend(_check_contract_invariants(app_dir))

    # Gate 5: License check (commercial mode only)
    commercial = os.environ.get("GAT_COMMERCIAL_MODE", "0") == "1"
    if commercial:
        all_checks.append(_check_license())
        all_checks.append(_check_compliance())

    failures = [c for c in all_checks if c.get("status") == "FAIL"]
    ok = len(failures) == 0

    return {
        "ok": ok,
        "total_checks": len(all_checks),
        "passed": sum(1 for c in all_checks if c.get("status") == "PASS"),
        "failed": len(failures),
        "skipped": sum(1 for c in all_checks if c.get("status") == "SKIP"),
        "checks": all_checks,
        "failures": [f"{c['check']}: {c.get('reason', 'unknown')}" for c in failures],
    }


def _check_license() -> dict:
    """Gate 5: Verify license is valid (commercial mode)."""
    try:
        from src.license.validator import load_license, get_license_status
        lic = load_license()
        if not lic:
            return {"check": "commercial/license", "status": "FAIL",
                    "reason": "No license found. Run: python3 -m src.license.activate --key <key>"}
        if lic.get("error"):
            return {"check": "commercial/license", "status": "FAIL",
                    "reason": f"License error: {lic['error']}"}
        return {"check": "commercial/license", "status": "PASS",
                "detail": get_license_status()}
    except ImportError as e:
        return {"check": "commercial/license", "status": "SKIP", "reason": str(e)}
    except Exception as e:
        return {"check": "commercial/license", "status": "FAIL", "reason": str(e)}


def _check_compliance() -> dict:
    """Gate 6: Verify risk disclosure accepted (commercial mode)."""
    try:
        from src.compliance.disclaimers import should_block_live_trading
        if should_block_live_trading():
            return {"check": "commercial/risk_disclosure", "status": "FAIL",
                    "reason": "Risk disclosure not accepted. Live trading blocked."}
        return {"check": "commercial/risk_disclosure", "status": "PASS"}
    except ImportError as e:
        return {"check": "commercial/risk_disclosure", "status": "SKIP", "reason": str(e)}
    except Exception as e:
        return {"check": "commercial/risk_disclosure", "status": "FAIL", "reason": str(e)}


if __name__ == "__main__":
    result = run_startup_gate()
    print(json.dumps(result, indent=2, ensure_ascii=False))
    sys.exit(0 if result["ok"] else 1)
