#!/usr/bin/env python3
"""Browser acceptance in an authorized local environment.
No policy changes, data-URL navigation or alternate transport to bypass a block.
Install Playwright and its Chromium normally before running this optional suite.
"""
from pathlib import Path
import argparse,asyncio,json,re
R=Path(__file__).resolve().parents[1]
async def run(executable=None):
 from playwright.async_api import async_playwright
 output={'status':'PENDING','scope':'This report website only, not Odoo or hardware','checks':[],'policy_modified':False,'browser_tests_passed':0,'console_errors':[],'external_requests':[]}
 def record(name,passed):output['checks'].append({'check':name,'status':'PASS' if passed else 'FAIL'})
 async with async_playwright() as pw:
  options={'headless':True}
  if executable:options['executable_path']=executable
  browser=await pw.chromium.launch(**options)
  context=await browser.new_context(viewport={'width':1440,'height':1000},accept_downloads=True,reduced_motion='reduce',offline=True)
  page=await context.new_page()
  page.on('pageerror',lambda e:output['console_errors'].append(str(e).replace(str(R),'endbericht')))
  page.on('request',lambda req: output['external_requests'].append(req.url.split('?')[0]) if req.url.startswith(('http:','https:')) else None)
  try:
   # Stop immediately on an administrative restriction. Do not try a new URL scheme.
   await page.goto((R/'index.html').as_uri(),wait_until='load',timeout=20000)
   nav=json.loads((R/'data/navigation.json').read_text())
   out=R/'proof/browser';out.mkdir(parents=True,exist_ok=True)
   for slug,_,_ in nav:
    await page.goto((R/(slug+'.html')).as_uri(),wait_until='load')
    record(slug+': one H1',await page.locator('h1').count()==1)
    record(slug+': correct active navigation',await page.locator('nav [aria-current=page]').get_attribute('href')==slug+'.html')
    record(slug+': no page overflow',await page.evaluate('document.documentElement.scrollWidth<=innerWidth+1'))
    await page.evaluate('async()=>{for(const im of document.querySelectorAll("img[src]")){im.loading="eager";try{await im.decode();}catch{}}}')
    record(slug+': local images decode',await page.evaluate('Array.from(document.querySelectorAll("img[src]")).filter(x=>x.getAttribute("src")).every(x=>x.complete&&x.naturalWidth>0)'))
    await page.screenshot(path=str(out/(slug+'-desktop.png')),full_page=True)
    await page.set_viewport_size({'width':390,'height':844})
    record(slug+': mobile no page overflow',await page.evaluate('document.documentElement.scrollWidth<=innerWidth+1'))
    await page.locator('[data-menu]').click();record(slug+': mobile menu opens',await page.locator('[data-menu]').get_attribute('aria-expanded')=='true');await page.keyboard.press('Escape')
    await page.screenshot(path=str(out/(slug+'-mobile.png')),full_page=True)
    await page.set_viewport_size({'width':1440,'height':1000})
   await page.goto((R/'funktion.html').as_uri())
   await page.locator('#matrix-status').select_option('blockiert')
   expected=len([x for x in json.loads((R/'data/funktionsmatrix.json').read_text()) if x['status']=='blockiert'])
   record('Feature status filter',await page.locator('#feature-matrix tbody tr:visible').count()==expected)
   async with page.expect_download() as down:
    await page.locator('[data-export=matrix-json]').click()
   file=await down.value;dest=R/'.build/browser-export.json';await file.save_as(dest);data=json.loads(dest.read_text());record('Export matches filtered rows',data['count']==expected and all(x['status']=='blockiert' for x in data['rows']))
   await page.locator('#matrix-search').fill('not-a-real-feature');record('Empty filter state',await page.locator('#matrix-empty').is_visible())
   await page.goto((R/'kaufmaennisch.html').as_uri());record('ROI initial hours',bool(re.search(r'31[,.]25',await page.locator('#roi-hours').inner_text())))
   await page.locator('#roi-minutes').evaluate('(el)=>el.value=0');await page.locator('#roi-minutes').dispatch_event('input');record('ROI zero benefit', 'Kein rechnerischer Ausgleich' in await page.locator('#roi-payback').inner_text())
   await page.locator('[data-roi-reset]').click();record('ROI reset',await page.locator('#roi-minutes').input_value()=='5')
   await page.goto((R/'technik.html').as_uri())
   for tab in await page.locator('[data-demo]').all():
    await tab.click();record('Demo tab '+(await tab.get_attribute('data-demo')),await tab.get_attribute('aria-selected')=='true')
   await page.locator('[data-fullscreen]').first.click();record('Demo fullscreen or fallback',await page.evaluate('Boolean(document.fullscreenElement||document.querySelector(".fullscreen-fallback"))'));await page.keyboard.press('Escape')
   await page.goto((R/'transkripte.html').as_uri());await page.locator('[data-copy]').first.click();record('Copy feedback visible',await page.locator('#toast').is_visible())
   await page.goto((R/'index.html').as_uri()+'?tour=kurz&step=0');record('Guided tour starts',await page.locator('.tour-panel').is_visible());await page.locator('[data-tour-next]').click();await page.wait_for_url('**/funktion.html?tour=kurz&step=1');record('Guided tour changes chapter',await page.locator('#tour-title').inner_text()=='Was funktioniert?');await page.keyboard.press('Escape');record('Guided tour exits',await page.locator('.tour-panel').count()==0)
   record('No external requests',not output['external_requests']);record('No uncaught JavaScript errors',not output['console_errors'])
   output['status']='PASS' if all(x['status']=='PASS' for x in output['checks']) else 'FAIL'
  except Exception as exc:
   msg=str(exc).replace(str(R),'endbericht')
   output['status']='BLOCKED' if 'ERR_BLOCKED_BY_ADMINISTRATOR' in msg else 'FAIL';output['error']=msg
  finally:
   output['browser_tests_passed']=sum(x['status']=='PASS' for x in output['checks']);await browser.close()
 (R/'proof/browser-checks.json').write_text(json.dumps(output,ensure_ascii=False,indent=2))
 print(json.dumps({'status':output['status'],'passed':output['browser_tests_passed'],'error':output.get('error','')},ensure_ascii=False))
 return 0 if output['status']=='PASS' else 2 if output['status']=='BLOCKED' else 1
if __name__=='__main__':
 parser=argparse.ArgumentParser();parser.add_argument('--executable',help='Optional installed Chromium executable. No policy flags are changed.');a=parser.parse_args();raise SystemExit(asyncio.run(run(a.executable)))
