#!/usr/bin/env python3
"""Run U60 lease/fencing on the existing loopback MySQL + Redis; record only synthetic observations."""
import hashlib
import json
import os
from pathlib import Path
import platform
import re
import shutil
import subprocess
import tempfile
import uuid
from datetime import datetime

ROOT = Path(__file__).resolve().parent
EVIDENCE = ROOT / 'evidence/u60-2026-10-31.json'
SOURCES = ['pom.xml', 'README.md', 'run.py', 'src/main/java/study/u60/LeaseFencingLab.java']
REQUIRED_JARS = ['jedis-7.0.0.jar', 'mysql-connector-j-9.7.0.jar']
MARKERS = ['schema-created=', 'mysql=', 'redis=', 'target=', 'leaseAcquire ', 'leaseRenew ',
           'leaseExpiry ', 'fencing ', 'opSeq ', 'idempotency ', 'cleanup ']


def diagnostics(output):
    if isinstance(output, bytes):
        output = output.decode('utf-8', 'replace')
    output = output or ''
    for secret in (os.environ.get('U60_MYSQL_PASSWORD', ''), os.environ.get('U60_MYSQL_USER', '')):
        if secret:
            output = output.replace(secret, '<REDACTED>')
    lines = [line for line in output.splitlines() if any(x in line for x in ('Exception', 'ERROR', 'Caused by:', 'AssertionError'))]
    return [re.sub(r'(?i)(password|pwd)=([^&\s]+)', r'\1=<REDACTED>', line)[:500] for line in lines[-6:]]


def main():
    java_home = Path(os.environ.get('JAVA_HOME', '/Library/Java/JavaVirtualMachines/liberica-jdk-21.jdk/Contents/Home'))
    java = java_home / 'bin/java'
    mvn = shutil.which('mvn')
    if not java.is_file() or not mvn:
        raise RuntimeError('需要 JDK 21 和 Maven 3.9，且 MySQL/Redis 由 DBngin 运行于 127.0.0.1')
    allowed = ('PATH', 'HOME', 'TMPDIR', 'LANG', 'LC_ALL', 'TZ', 'U60_MYSQL_USER', 'U60_MYSQL_PASSWORD')
    env = {key: os.environ[key] for key in allowed if key in os.environ}
    env.update(JAVA_HOME=str(java_home), MAVEN_OPTS='-Xmx384m -XX:ActiveProcessorCount=2')
    java_version = subprocess.check_output([str(java), '-version'], stderr=subprocess.STDOUT, text=True, env=env).strip()
    if '21.0.11' not in java_version:
        raise RuntimeError('实验基线需要 JDK 21.0.11')
    result = {'issue': 97, 'input': 'python3 labs/correctness-u60/run.py',
              'startedAt': datetime.now().astimezone().isoformat(),
              'environment': {'os': platform.platform(), 'arch': platform.machine()}, 'status': 'failed', 'exitCode': 1,
              'observations': []}
    schema = 'study_u60_' + uuid.uuid4().hex[:12]
    result['schema'] = schema
    try:
        with tempfile.TemporaryDirectory(prefix='correctness-u60-') as directory:
            cp = Path(directory) / 'classpath.txt'
            cmd = [mvn, '-o', '-B', '-q', 'compile', 'org.apache.maven.plugins:maven-dependency-plugin:3.8.1:build-classpath',
                   '-DincludeScope=runtime', f'-Dmdep.outputFile={cp}']
            subprocess.run(cmd, cwd=ROOT, env=env, check=True, timeout=180, capture_output=True, text=True)
            classpath = str(ROOT / 'target/classes') + os.pathsep + cp.read_text().strip()
            versions = [x for x in cp.read_text().split(os.pathsep)
                        if any(k in x for k in ['/jedis/', '/mysql-connector-j/'])]
            result['dependencies'] = [Path(x).name for x in versions]
            for required in REQUIRED_JARS:
                if not any(required in d for d in result['dependencies']):
                    raise RuntimeError('依赖版本不符: ' + required + '; actual=' + str(result['dependencies']))
            command = [str(java), '-Dspring.config.location=optional:file:/dev/null', '-Xms64m', '-Xmx256m',
                       '-XX:ActiveProcessorCount=2', '-cp', classpath, 'study.u60.LeaseFencingLab', schema, '6379']
            returncode = None
            result['phase'] = 'java-lab'
            stderr = ''
            try:
                run = subprocess.run(command, cwd=ROOT, env=env, capture_output=True, text=True, timeout=120)
                stdout, stderr, returncode = run.stdout, run.stderr, run.returncode
            except subprocess.TimeoutExpired as exc:
                stdout = (exc.stdout or b'').decode('utf-8', 'replace') if isinstance(exc.stdout, bytes) else (exc.stdout or '')
                stderr = exc.stderr or ''
            result['observations'] = [line for line in stdout.splitlines() if line.startswith('U60 ')]
            cleaned = 'U60 cleanup own-schema=removed' in result['observations'] and 'U60 cleanup redisKeys=removed' in result['observations']
            if returncode != 0 or not all(any(line.startswith('U60 ' + m) for line in result['observations']) for m in MARKERS):
                result['diagnostics'] = diagnostics(stderr) + diagnostics(stdout)
                raise RuntimeError('实验进程失败或观察缺失；exit=' + str(returncode)
                                   + ('；本次 schema 已清理' if cleaned else '；请核对 schema ' + schema))
            result.update(status='passed', exitCode=0, java=java_version,
                          limits='单机 Redis standalone 作为授权权威（其自身故障即权威不可用，活性边界在正文声明）；MySQL 单机资源；未运行跨主机网络故障、多权威共识、时钟注入与生产容量。',
                          artifactSha256={p: hashlib.sha256((ROOT / p).read_bytes()).hexdigest() for p in SOURCES if (ROOT / p).exists()})
    except Exception as exc:
        if isinstance(exc, subprocess.CalledProcessError):
            result['phase'] = 'maven'
            result['diagnostics'] = diagnostics(exc.stderr) + diagnostics(exc.stdout)
        result['error'] = type(exc).__name__ + ': ' + str(exc)
        raise
    finally:
        if result['status'] == 'passed':
            EVIDENCE.parent.mkdir(parents=True, exist_ok=True)
            tmp = EVIDENCE.with_suffix('.tmp')
            tmp.write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n')
            tmp.replace(EVIDENCE)
        else:
            print(json.dumps(result, ensure_ascii=False, indent=2))
    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == '__main__':
    main()
