#!/usr/bin/env python3
"""Run U42 on the existing loopback MySQL; 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/u42-2026-10-30.json'
SOURCES = ['pom.xml', 'README.md', 'run.py', 'src/main/java/study/u42/U42Lab.java',
           'src/main/java/study/u42/TracePlugins.java', 'src/main/java/study/u42/U42Cleanup.java', 'src/main/java/study/u42/mapper/Item.java', 'src/main/java/study/u42/mapper/Category.java', 'src/main/java/study/u42/mapper/Status.java',
           'src/main/java/study/u42/mapper/ItemMapper.java', 'src/main/java/study/u42/mapper/ItemSql.java',
           'src/main/resources/study/u42/mapper/ItemMapper.xml', 'src/main/resources/study/u42/native-config.xml', 'src/main/resources/study/u42/native.properties']

def diagnostics(output):
    if isinstance(output, bytes): output = output.decode('utf-8', 'replace')
    output = output or ''
    for secret in (os.environ.get('U42_MYSQL_PASSWORD', ''), os.environ.get('U42_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:', 'error', '拒绝'))]
    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 由 DBngin 运行于 127.0.0.1:3306')
    env = dict(os.environ, JAVA_HOME=str(java_home), MAVEN_OPTS='-Xmx384m -XX:ActiveProcessorCount=2')
    result = {'issue': 79, 'input': 'python3 labs/mybatis-u42/run.py', 'startedAt': datetime.now().astimezone().isoformat(),
              'environment': {'os': platform.platform(), 'arch': platform.machine()}, 'status': 'failed', 'exitCode': 1,
              'observations': []}
    schema = 'study_u42_' + uuid.uuid4().hex[:12]
    result['schema'] = schema
    try:
        with tempfile.TemporaryDirectory(prefix='mybatis-u42-') 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', 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 ['/mybatis/', '/mybatis-spring/', '/mybatis-spring-boot-starter/', '/spring-jdbc/', '/mysql-connector-j/'])]
            result['dependencies'] = [Path(x).name for x in versions]
            for required in ['mybatis-3.5.19.jar', 'mybatis-spring-4.0.0.jar', 'mybatis-spring-boot-starter-4.0.1.jar', 'spring-jdbc-7.0.10.jar', 'mysql-connector-j-9.7.0.jar']:
                if required not in result['dependencies']: raise RuntimeError('依赖版本不符: ' + required)
            # Harmless nonexistent override tests that Boot cannot redirect this lab's explicit DataSource.
            env['SPRING_DATASOURCE_URL'] = 'jdbc:mysql://127.0.0.1:3306/u42_must_never_connect'
            command = [str(java), '-Xms64m', '-Xmx256m', '-XX:ActiveProcessorCount=2', '-cp', classpath, 'study.u42.U42Lab', schema]
            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('U42 ')]
            created = 'U42 schema-created=' + schema in result['observations']
            cleaned = 'U42 cleanup=PASS own-schema=removed' in result['observations']
            if created and not cleaned:
                rescue = subprocess.run([str(java), '-Xmx64m', '-cp', classpath, 'study.u42.U42Cleanup', schema], cwd=ROOT, env=env, capture_output=True, text=True, timeout=30)
                if rescue.returncode != 0:
                    result['diagnostics'] = diagnostics(rescue.stderr + rescue.stdout)
                    raise RuntimeError('本次 schema 应急清理失败，请检查 ' + schema)
                result['observations'].append('U42 emergency-cleanup=PASS own-schema=removed')
            markers = ['schema-created=', 'mysql=', 'target=PASS', 'plugins=PASS', 'config=PASS', 'dynamic=PASS', 'mapping=PASS', 'association=PASS', 'scan=PASS', 'translate=PASS', 'transaction=PASS', 'cursor=PASS', 'cleanup=PASS']
            if returncode != 0 or not all(any(line.startswith('U42 ' + m) for line in result['observations']) for m in markers):
                result['diagnostics'] = diagnostics(stderr) + diagnostics(stdout)
                raise RuntimeError('实验进程失败或观察缺失；exit=' + str(returncode) + ('；本次 schema 已清理' if cleaned or created else '；未观察到创建标记，请核对 schema ' + schema))
            result.update(status='passed', exitCode=0, java=subprocess.check_output([str(java), '-version'], stderr=subprocess.STDOUT, text=True).strip(),
                limits='MySQL 8.4.7 loopback 单机；未验证跨主机故障、生产容量、异数据源事务或新版二级缓存提交失败窗口。',
                artifactSha256={p: hashlib.sha256((ROOT / p).read_bytes()).hexdigest() for p in SOURCES})
    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()
