#!/usr/bin/env python3
"""Run the isolated Dubbo 3.2.15 direct provider/consumer experiment."""
import hashlib
import json
import os
import pathlib
import platform
import shutil
import socket
import subprocess
import tempfile
from datetime import datetime

ROOT = pathlib.Path(__file__).resolve().parent
EVIDENCE = ROOT / "evidence/u41-2026-10-30.json"


def digest(path):
    return hashlib.sha256(path.read_bytes()).hexdigest()


def port():
    sock = socket.socket()
    sock.bind(("127.0.0.1", 0))
    value = sock.getsockname()[1]
    sock.close()
    return value


def main():
    java_home = pathlib.Path(os.environ.get("JAVA_HOME", "/Library/Java/JavaVirtualMachines/liberica-jdk-17.jdk/Contents/Home"))
    maven = shutil.which("mvn")
    java = java_home / "bin/java"
    if not maven or not java.exists():
        raise RuntimeError("需要 JDK 17 与 Maven 3.9.x")
    started = datetime.now().astimezone().isoformat()
    evidence = {"issue": 78, "input": "python3 labs/dubbo/run.py", "status": "failed", "startedAt": started,
                "environment": {"os": platform.platform(), "arch": platform.machine()}, "observations": [], "exitCode": 1}
    with tempfile.TemporaryDirectory(prefix="dubbo-u41-") as directory:
        work = pathlib.Path(directory)
        env = dict(os.environ, JAVA_HOME=str(java_home), MAVEN_OPTS="-Xmx384m -XX:ActiveProcessorCount=2")
        try:
            cp_file = work / "classpath.txt"
            subprocess.run([maven, "-o", "-B", "-q", f"-Dmaven.repo.local={pathlib.Path.home() / '.m2/repository'}", "compile", "dependency:build-classpath", f"-Dmdep.outputFile={cp_file}"], cwd=ROOT, env=env, check=True, timeout=180)
            cp = str(ROOT / "target/classes") + os.pathsep + cp_file.read_text().strip()
            first_port = port()
            second_port = port()
            while second_port == first_port:
                second_port = port()
            run = subprocess.run([str(java), "-Xms64m", "-Xmx256m", "-XX:ActiveProcessorCount=2", "--add-opens=java.base/java.math=ALL-UNNAMED", "--add-opens=java.base/java.util=ALL-UNNAMED", "--add-opens=java.base/java.lang=ALL-UNNAMED", "-cp", cp, "study.dubbo.DubboLab", str(first_port), str(second_port)], cwd=ROOT, env=env, text=True, capture_output=True, timeout=180)
            output = run.stdout + run.stderr
            evidence["observations"] = [line for line in output.splitlines() if line.startswith("U41 ")]
            if run.returncode:
                errors = [line for line in output.splitlines() if "Exception" in line or "Caused by:" in line]
                raise RuntimeError(f"DubboLab exit={run.returncode}\n" + "\n".join(errors[-8:]))
            expected = ["U41 direct=PASS", "U41 retry=PASS", "U41 drain=PASS", "U41 unexport=PASS", "U41 cleanup=PASS", "U41 direct-mock-counterexample=PASS", "U41 recovery=PASS", "U41 recovery-cleanup=PASS"]
            if not all(any(line.startswith(marker) for line in evidence["observations"]) for marker in expected):
                raise RuntimeError("缺少实验观察行")
            evidence.update(status="passed", exitCode=0, dubbo="3.2.15 direct loopback; no registry", java=subprocess.check_output([str(java), "-version"], stderr=subprocess.STDOUT, text=True).strip(), limits="同一 JVM、同一主机、直连协议；未模拟注册中心分区、跨主机网络故障、真实丢响应或生产容量。", artifactSha256={str(p.relative_to(ROOT)): digest(p) for p in [ROOT / "pom.xml", ROOT / "README.md", ROOT / "run.py", ROOT / "src/main/java/study/dubbo/DubboLab.java"]})
        except Exception as exc:
            evidence["error"] = str(exc)
            raise
        finally:
            EVIDENCE.parent.mkdir(parents=True, exist_ok=True)
            EVIDENCE.write_text(json.dumps(evidence, indent=2, ensure_ascii=False) + "\n")
    print(json.dumps(evidence, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()
