"""Execute local operating scenarios, including abrupt exit and simultaneous imports."""
from pathlib import Path
import base64,hashlib,json,os,platform,re,sqlite3,subprocess,sys,tempfile,time
from html import escape
from fixtures import create,xml
import triage
ROOT=Path(__file__).resolve().parent
checks=[]
def check(key,description,expected,observed,method):
    passed=expected==observed
    checks.append(dict(id=key,description=description,expected=expected,observed=observed,passed=passed,method=method))
    if not passed:raise AssertionError((key,expected,observed))

def main():
    with tempfile.TemporaryDirectory(prefix='ci-triage-acceptance-') as temp:
        work=Path(temp);examples=work/'examples';manifest=create(examples);db=work/'history.sqlite'
        for item in manifest:triage.ingest(db,[examples/n for n in item['files']],**{k:v for k,v in item.items() if k!='files'})
        data=triage.snapshot(db);latest=data['selections']['build-105']
        check('classifications','Final fixture classification matches an independent expected table',{'new failure':2,'repeated failure':1,'failure without baseline':0,'retry pass':1,'recovered':1,'pass':14,'skipped':1,'not reported':1},latest['counts'],'Five synthetic builds use known fixture statuses and explicit full/partial modes.')
        check('retained-history','Earlier attempts remain inspectable after partial retry',12,len(data['artifacts']),'Original artifacts are retained as immutable BLOBs; twelve ingested job attempts.')
        duplicate=[triage.ingest(db,[examples/n for n in item['files']],**{k:v for k,v in item.items() if k!='files'})['duplicate'] for item in manifest]
        check('idempotent-reimport','Repeating all twelve imports adds no observations',[True,102],[all(duplicate),sum(x['observations'] for x in triage.snapshot(db)['selections'].values())],'Repeat exact basename/content/mode identities against the committed database.')
        before=triage.snapshot(db);conflict=work/'conflict.xml';conflict.write_bytes(xml([{'name':'replacement'}]));rejected=False
        try:triage.ingest(db,[conflict],build='build-105',sequence=105,job='api-linux',attempt=1)
        except triage.Conflict:rejected=True
        check('immutable-attempt','A different artifact cannot overwrite an existing attempt',[True,True],[rejected,before==triage.snapshot(db)],'Attempt key is build/job/attempt; complete artifact fingerprint and mode must agree.')
        bad=work/'bad.xml';bad.write_text('<testsuite>');rejected=False
        try:triage.ingest(db,[bad],build='bad',sequence=106,job='api')
        except triage.InvalidReport:rejected=True
        check('malformed-input','Malformed XML has no database side effects',[True,True],[rejected,before==triage.snapshot(db)],'Parse and validate every artifact before beginning the ingestion transaction.')
        oversized=work/'oversized.xml';oversized.write_bytes(b' '*(triage.MAX_BYTES+1));result=subprocess.run([sys.executable,str(ROOT/'triage.py'),'ingest',str(oversized),'--db',str(db),'--build','too-big','--sequence','106','--job','api'],capture_output=True,text=True)
        check('oversized-input','An actual file above 10 MiB is rejected by the CLI',[2,True],[result.returncode,before==triage.snapshot(db)],'Bounded file read and combined-attempt size check, using the production limit.')
        child=work/'crash.py';child.write_text('''import os,sqlite3,sys\nimport triage\nclass Crash(sqlite3.Connection):\n def execute(self,sql,parameters=()):\n  result=super().execute(sql,parameters)\n  if sql.startswith('INSERT INTO cases'):os._exit(86)\n  return result\ndef connection(path,create=True):\n db=sqlite3.connect(path,isolation_level=None,factory=Crash);db.row_factory=sqlite3.Row;db.execute('PRAGMA foreign_keys=ON');return db\ntriage.connect=connection\ntriage.ingest(sys.argv[1],[sys.argv[2]],build='crash-build',sequence=106,job='api')\n''')
        env={**os.environ,'PYTHONPATH':str(ROOT)};result=subprocess.run([sys.executable,str(child),str(db),str(conflict)],env=env,capture_output=True,text=True)
        check('abrupt-exit','Abrupt exit after a case insert leaves no partial build or artifact',[86,True],[result.returncode,before==triage.snapshot(db)],'A subprocess calls os._exit(86) inside the transaction, before COMMIT. SQLite recovers the journal on reopen; this is not a physical power-loss test.')
        resumed=triage.ingest(db,[conflict],build='crash-build',sequence=106,job='api')
        check('resume-after-exit','The interrupted identity can be imported once after restart',[True,6],[resumed['ingested'],len(triage.snapshot(db)['builds'])],'Normal ingestion after recovery and read-only analysis.')
        concurrent_db=work/'concurrent.sqlite';triage.connect(concurrent_db).close()
        args=[sys.executable,str(ROOT/'triage.py'),'ingest',str(conflict),'--db',str(concurrent_db),'--build','parallel','--sequence','1','--job','api']
        children=[subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True) for _ in range(2)]
        responses=[]
        for process in children:
            stdout,stderr=process.communicate(timeout=10)
            if process.returncode:raise AssertionError(stderr)
            responses.append(json.loads(stdout))
        check('concurrent-identical-import','Two concurrent importers commit one attempt and return one duplicate',[1,1,1],[sum(r['ingested'] for r in responses),sum(r['duplicate'] for r in responses),len(triage.snapshot(concurrent_db)['artifacts'])],'Two real Python processes write the same database; BEGIN IMMEDIATE serializes comparison and insertion.')
        output=work/'review';triage.export(db,output);html=(output/'index.html').read_text();embedded=json.loads(re.search(r'<script id="data" type="application/json">(.*?)</script>',html,re.S)[1])
        check('embedded-artifact-roundtrip','Every embedded XML download reproduces its committed SHA-256',True,all(hashlib.sha256(base64.b64decode(a['content'])).hexdigest()==a['sha256'] for a in embedded['artifacts']),'Parse the exact generated HTML JSON payload, decode all artifact bytes and recompute digests.')
        digest=hashlib.sha256((output/'index.html').read_bytes()).hexdigest();triage.export(db,output)
        check('deterministic-export','Unchanged committed history produces identical HTML',digest,hashlib.sha256((output/'index.html').read_bytes()).hexdigest(),'No wall-clock timestamp, random IDs or network state enters report generation.')
        result=subprocess.run([sys.executable,str(ROOT/'triage.py'),'check','--db',str(db),'--build','build-105'],capture_output=True,text=True)
        check('ci-exit-code','Check returns a failing job status for final failures',[1,3],[result.returncode,json.loads(result.stdout)['failed']],'The CLI independently reports final failure/error count, without making XML ingestion itself a release gate.')
        large=work/'large.xml';large.write_bytes(xml([{'name':f'test_case_{i:05d}','status':'failure' if i%1000==0 else 'pass','duration':.001} for i in range(10000)]));large_db=work/'large.sqlite'
        start=time.perf_counter();triage.ingest(large_db,[large],build='batch',sequence=1,job='unit');ingest_seconds=time.perf_counter()-start
        start=time.perf_counter();large_data=triage.snapshot(large_db);analysis_seconds=time.perf_counter()-start
        check('large-batch-count','Ten thousand synthetic results retain identities and ten failures',[10000,10],[len(large_data['selections']['batch']['rows']),large_data['selections']['batch']['failed']],'One generated XML file, actual local SQLite ingest and full analysis. Timing is an observation, not a service-level commitment.')
        measurements={'tests':10000,'input_bytes':large.stat().st_size,'ingest_seconds':round(ingest_seconds,6),'analysis_seconds':round(analysis_seconds,6),'database_bytes':large_db.stat().st_size,'python':platform.python_version(),'sqlite':sqlite3.sqlite_version}
    sources=['triage.py','report.py','fixtures.py','run_demo.py','acceptance.py','test_triage.py','verify.py','README.md']
    record={'schema_version':1,'evidence_type':'executed synthetic operations','command':'python acceptance.py','passed':all(c['passed'] for c in checks),'checks':checks,'source_sha256':{n:hashlib.sha256((ROOT/n).read_bytes()).hexdigest() for n in sources},'report':'completion/report.html','limitations':['Synthetic local fixtures, not production CI data.','No provider API connector, automatic branch/commit comparison, root-cause diagnosis, or release certification.','Abrupt process exit tests transaction recovery, not physical power-loss durability.','Per-attempt resource limits do not impose an unlimited-history database or browser capacity guarantee.'],'results':measurements}
    (ROOT/'completion').mkdir(exist_ok=True);(ROOT/'completion.json').write_text(json.dumps(record,indent=2)+'\n');(ROOT/'results/acceptance-measurements.json').write_text(json.dumps(measurements,indent=2)+'\n')
    from report import CSS
    entries=''.join(f'<section><h2>{escape(c["description"])}</h2><p>{escape(c["method"])}</p><pre>Expected: {escape(json.dumps(c["expected"]))}\nObserved: {escape(json.dumps(c["observed"]))}</pre><p class="live">Matched expected outcome</p></section>' for c in checks)
    (ROOT/'completion/report.html').write_text(f'<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>CI triage operating checks</title><style>{CSS}section{{padding:24px 0;border-bottom:1px solid var(--line)}}pre{{overflow-wrap:anywhere}}main{{max-width:960px}}</style></head><body><header><div><p class="eyebrow">EXECUTED SYNTHETIC OPERATIONS</p><h1>CI triage operating checks</h1></div></header><main><section><p>{len(checks)} checks matched their expected outcomes. Rejected inputs and interrupted transactions are intentional acceptance cases.</p><p><a href="../report.html">Open the interactive reference report</a> / <a href="../completion.json">Machine-readable record</a></p><pre>{escape(json.dumps(measurements,indent=2))}</pre><p>Timings describe one local run on the available runtime. They are not measured production CI performance.</p></section>{entries}<section><h2>Boundaries</h2>'+''.join('<p>'+escape(x)+'</p>' for x in record['limitations'])+'</section></main><footer>Source hashes and exact expected/observed values are included in completion.json.</footer></body></html>')
    print(json.dumps({'passed':len(checks),'measurements':measurements},indent=2))
if __name__=='__main__':main()
