#!/usr/bin/env python3
import hashlib, json, re, sys
from pathlib import Path
root=Path(sys.argv[1] if len(sys.argv)>1 else '').resolve(); errors=[]
def fail(code,detail): errors.append(f'{code}:{detail}')
def text(path):
    try: return path.read_text(encoding='utf-8')
    except UnicodeDecodeError: return ''
for rel in ('install.sh','manifest.sha256','release-info.json','tests/mandatory.list'):
    if not (root/rel).is_file(): fail('missing_required',rel)
if errors:
    for e in errors: print(f'POLICY_ERROR={e}',file=sys.stderr)
    print('RESULT=STOP_ROUTER_BUNDLE_POLICY_R19',file=sys.stderr); raise SystemExit(40)
try: info=json.loads((root/'release-info.json').read_text(encoding='utf-8'))
except Exception as exc: fail('release_info_invalid',str(exc)); info={}
mandatory=[]
for raw in (root/'tests/mandatory.list').read_text(encoding='utf-8').splitlines():
    item=raw.strip()
    if not item or item.startswith('#'): continue
    if item in mandatory: fail('mandatory_duplicate',item)
    mandatory.append(item)
    if not (root/'tests'/item).is_file(): fail('mandatory_missing',item)
if not mandatory: fail('mandatory_empty','tests/mandatory.list')
diagp=root/'tests/diagnostic.list'
if diagp.is_file():
    diag={x.strip() for x in diagp.read_text(encoding='utf-8').splitlines() if x.strip() and not x.lstrip().startswith('#')}
    overlap=sorted(diag.intersection(mandatory))
    if overlap: fail('mandatory_diagnostic_overlap',','.join(overlap))
production=[root/'install.sh']
for sub in ('scripts','rollback','payload/rootfs'):
    base=root/sub
    if base.exists(): production.extend(p for p in base.rglob('*') if p.is_file() and 'selftest' not in p.name and (p.suffix=='.sh' or p.name.startswith('router-') or p.name=='install.sh'))
shell_files=[]
for p in production:
    t=text(p); first=t.splitlines()[0] if t.splitlines() else ''
    if first.startswith('#!') and ('sh' in first or 'bash' in first): shell_files.append((p,t))
unzip_re=re.compile(r'(^|[\s;&|])unzip([\s]|$)',re.M)
for p,t in shell_files:
    if unzip_re.search(t): fail('unzip_dependency',str(p.relative_to(root)))
remote_forbidden=[(re.compile(r'find[^\n]*(?:-printf|-quit)'),'busybox_find_gnu_option'),(re.compile(r'grep\s+-P'),'grep_pcre'),(re.compile(r'(^|[;\s])\[\[(?=\s)'),'bash_double_bracket'),(re.compile(r'\bmapfile\b'),'bash_mapfile'),(re.compile(r'<\('),'process_substitution')]
for p,t in shell_files:
    first=t.splitlines()[0] if t.splitlines() else ''
    remote=(p.parent.name=='scripts' and first in ('#!/bin/sh','#!/bin/ash'))
    if remote:
        for rx,code in remote_forbidden:
            if rx.search(t): fail(code,str(p.relative_to(root)))
for p,t in shell_files:
    if '/root/.vm101-source.git' in t and 'ls-tree' in t:
        if '--full-tree' not in t: fail('machine_git_missing_full_tree',str(p.relative_to(root)))
        if 'git -C /' not in t and '--work-tree=/' not in t and '--work-tree /' not in t: fail('machine_git_missing_root_context',str(p.relative_to(root)))
for p,t in shell_files:
    for n,line in enumerate(t.splitlines(),1):
        s=line.strip()
        if not s or s.startswith('#'): continue
        if 'vm101_run_command' in s and re.search(r'[|;`]|\$\(',s): fail('complex_nested_remote_command',f'{p.relative_to(root)}:{n}')
        if re.search(r'router-machine-close(?!-safe)',s) and p.name!='router-machine-close-safe': fail('direct_machine_close_forbidden',f'{p.relative_to(root)}:{n}')
installer=text(root/'install.sh')
if re.search(r'^stop\s*\(\)\s*\{',installer,re.M):
    count=len(re.findall(r'^stop\s*\(\)\s*\{',installer,re.M))
    if count!=1: fail('stop_handler_count',str(count))
    err=[x for x in installer.splitlines() if re.search(r'^\s*trap\s+.+\sERR\s*$',x) and 'trap - ERR' not in x]
    if len(err)!=1: fail('err_trap_count',str(len(err)))
if info.get('classification')=='continuation':
    if not info.get('previous_step'): fail('continuation_previous_step_missing','release-info.json')
    if not info.get('first_unfinished_phase'): fail('continuation_first_unfinished_phase_missing','release-info.json')
    for key in ('core_change_repeated','target_installer_rerun','target_postcheck_repeated','router_machine_close_rerun'):
        if info.get(key) is True: fail('continuation_repeat_forbidden',key)
packages=sorted((root/'payload').rglob('*target-package*.tar.gz')) if (root/'payload').exists() else []
if packages:
    if len(packages)!=1: fail('target_package_count',str(len(packages)))
    else:
        pkg=packages[0]; actual=hashlib.sha256(pkg.read_bytes()).hexdigest()
        if info.get('target_package_sha256')!=actual: fail('target_package_release_sha_mismatch',actual)
        ref=root/'reference/target-package.sha256'; expected=f'{actual}  {pkg.relative_to(root).as_posix()}'
        if not ref.is_file(): fail('target_package_reference_missing',str(ref.relative_to(root)))
        elif ref.read_text(encoding='utf-8').strip()!=expected: fail('target_package_reference_mismatch',str(ref.relative_to(root)))
        applies=[p for p in (root/'scripts').glob('*apply*.sh')] if (root/'scripts').exists() else []
        if not applies: fail('target_apply_script_missing','scripts/*apply*.sh')
        elif not any('EXPECTED_PACKAGE_SHA="${1:?' in text(p) or re.search(r'EXPECTED_(?:PACKAGE|TARGET)_SHA=.*\$\{?1',text(p)) for p in applies): fail('target_apply_sha_not_argument',','.join(str(p.relative_to(root)) for p in applies))
        if actual not in installer: fail('target_package_sha_missing_from_installer','install.sh')
for p,t in shell_files:
    if re.search(r'^rollback_now\s*\(\)',t,re.M):
        if 'BACKUP_READY=false' not in t: fail('rollback_backup_ready_init_missing',str(p.relative_to(root)))
        guarded=('[ "$BACKUP_READY" = true ] || return 0' in t or '[ "$BACKUP_READY" = "true" ] || return 0' in t or 'if [ "$BACKUP_READY" != true ]' in t or 'if [ "$BACKUP_READY" != "true" ]' in t)
        if not guarded: fail('rollback_backup_ready_guard_missing',str(p.relative_to(root)))
all_prod='\n'.join(t for _,t in shell_files)
if 'hmn-rank-awg' in all_prod and ('provider-results-schema-adapter' not in all_prod and 'PROVIDER_RESULTS_SCHEMA_ADAPTER=true' not in all_prod and 'provider_results_schema_adapter' not in json.dumps(info,sort_keys=True)): fail('provider_schema_adapter_missing','hmn-rank-awg')
for p,t in shell_files:
    if 'STOP_ACTIVE_GENERATION_PATH_INVALID' in t and 'readlink -f' in t and 'GENERATION_STAGING_DIR' in t and not re.search(r'(STAGING_REAL|staging_real|router_path_is_within)',t): fail('logical_real_path_normalization_missing',str(p.relative_to(root)))
if 'GENERATION_ACTIVATION_AUTH_DIR' in all_prod and 'ROUTER_EGRESS_ACTIVATION_AUTH_DIR' not in all_prod: fail('activation_auth_variable_contract','GENERATION_ACTIVATION_AUTH_DIR')
if info.get('runtime_impact') is True and 'router-runtime-transition.sh' not in installer: fail('runtime_transition_library_required','install.sh')
if errors:
    for e in errors: print(f'POLICY_ERROR={e}',file=sys.stderr)
    print('RESULT=STOP_ROUTER_BUNDLE_POLICY_R19',file=sys.stderr); raise SystemExit(41)
print('RESULT=PASS_ROUTER_BUNDLE_POLICY_R19'); print(f'MANDATORY_FIXTURE_COUNT={len(mandatory)}'); print('R19_SHARED_POLICY_APPLIED=true')
