import json
from pathlib import Path
from playwright.sync_api import sync_playwright

ROOT=Path(__file__).resolve().parents[1]
BASE='http://127.0.0.1:8138/'
results=[]
def rec(name,ok,detail=None): results.append({'name':name,'passed':bool(ok),'detail':detail})

with sync_playwright() as p:
    b=p.chromium.launch(headless=True,executable_path='/usr/bin/chromium',args=['--no-sandbox','--disable-dev-shm-usage','--disable-gpu'])

    # Responsive board + route-stage semantics.
    for width,height in [(390,844),(768,900),(1024,900),(1440,900),(1920,1080)]:
        page=b.new_page(viewport={'width':width,'height':height})
        errors=[]
        page.on('pageerror',lambda e: errors.append(str(e)))
        page.on('console',lambda m: errors.append(m.text) if m.type=='error' else None)
        page.goto(BASE,wait_until='domcontentloaded',timeout=10000);page.wait_for_timeout(140)
        m=page.evaluate('''() => ({sw:document.documentElement.scrollWidth,cw:document.documentElement.clientWidth,cards:document.querySelectorAll('.entry-package').length})''')
        rec(f'layout-{width}',m['sw']<=m['cw']+1 and m['cards']==6 and not errors,{'metrics':m,'errors':errors})
        if width in (1440,1920):
            box=page.locator('.entry-package-grid').bounding_box();rec(f'board-in-first-viewport-{width}',bool(box) and box['y']+box['height']<=height+1,box)
        if width==390:
            cols=page.locator('.entry-package-grid').evaluate("e=>getComputedStyle(e).gridTemplateColumns.split(' ').length")
            rec('mobile-two-columns',cols==2,cols)
        page.close()

    page=b.new_page(viewport={'width':1440,'height':1000})
    page.goto(BASE,wait_until='domcontentloaded',timeout=10000);page.wait_for_timeout(120)
    rec('six-entry-options',page.locator('[data-preset]').count()==6,page.locator('[data-preset]').count())
    rec('custom-copy-starts-zero','0 услуг выбрано на старте' in page.locator('[data-preset="custom"]').locator('xpath=ancestor::article').inner_text(),page.locator('[data-preset="custom"]').locator('xpath=ancestor::article').inner_text())
    route_labels=page.locator('.entry-package').first.locator('.route-dot').evaluate_all("els=>els.map(e=>e.dataset.routeLabel)")
    rec('route-stage-labels-explicit',route_labels==['Документы','Приезд','Обустройство','Беременность','Подготовка к родам','Роды и новорождённый','Восстановление','Concierge / Premium'],route_labels)
    rec('route-dots-focusable',all(x=='0' for x in page.locator('.entry-package').first.locator('.route-dot').evaluate_all("els=>els.map(e=>e.getAttribute('tabindex'))")),None)

    # Documents and Custom must have materially different starting selections.
    page.click('[data-preset="documents"]');page.wait_for_timeout(60)
    rec('documents-starts-four',page.locator('.editable-service.selected').count()==4,page.locator('.editable-service.selected').count())
    rec('progress-starts-one-of-six',page.locator('#progressTitle').inner_text()=='Шаг настройки 1 из 6',page.locator('#progressTitle').inner_text())
    rec('no-user-preset-badges',page.locator('.preset-badge').count()==0,page.locator('.preset-badge').count())
    page.click('#restartTopBtn');page.click('#resetConfirmBtn');page.wait_for_timeout(50)
    page.click('[data-preset="custom"]');page.wait_for_timeout(60)
    rec('custom-starts-empty',page.locator('.editable-service.selected').count()==0,page.locator('.editable-service.selected').count())
    rec('custom-summary-says-from-zero','собрано с нуля' in page.locator('.section-compact-summary').inner_text().lower(),page.locator('.section-compact-summary').inner_text())
    page.click('#restartTopBtn');page.click('#resetConfirmBtn');page.wait_for_timeout(50)

    # Birth flow with one remove, one Complete-level add, one fixed extra.
    page.click('[data-preset="birth"]');page.wait_for_timeout(60)
    page.locator('[data-service]').first.click();page.wait_for_timeout(40)
    for expected,n in [('Приезд и быт',2),('Беременность',3),('Роды',4),('Комфорт',5)]:
        page.click('#nextBtn');page.wait_for_timeout(40)
        rec(f'progress-{n}-of-six',page.locator('#progressTitle').inner_text()==f'Шаг настройки {n} из 6',page.locator('#progressTitle').inner_text())
        rec(f'progress-name-{n}',page.locator('#progressText').inner_text()==expected,page.locator('#progressText').inner_text())
    page.locator('[data-service]:not(.selected)').first.click();page.click('#nextBtn');page.wait_for_timeout(45)
    rec('addons-is-step-six',page.locator('#progressTitle').inner_text()=='Шаг настройки 6 из 6',page.locator('#progressTitle').inner_text())
    addon_text=page.locator('#wizardStage').inner_text()
    rec('addons-client-language','фиксированной ценой' in addon_text.lower() and 'исходном документе' not in addon_text.lower(),addon_text[:400])
    page.click('[data-add="women"]');page.click('#nextBtn');page.wait_for_timeout(80)

    # Confirmation is outside configuration step counter.
    rec('result-progress-hidden',not page.locator('.wizard-progress').is_visible(),page.locator('.wizard-progress').is_visible())
    rec('result-source-birth','На базе Birth Experience' in page.locator('.confirmation-source').inner_text(),page.locator('.confirmation-source').inner_text())
    econ=page.locator('.economics-card').inner_text()
    rec('benchmark-wording','Пакетный ориентир' in econ and 'Текущий состав услуг' not in econ,econ)
    rec('benchmark-caveat','не расчёт стоимости каждой выбранной услуги' in econ.lower(),econ)
    rec('fixed-extras-wording','Фиксированные доп. услуги' in econ,econ)
    rec('changes-visible',page.locator('.change-list .delta-added').count()>=1 and page.locator('.change-list .delta-removed').count()>=1,{'added':page.locator('.change-list .delta-added').count(),'removed':page.locator('.change-list .delta-removed').count()})
    closed_details=page.locator('.unchanged-details:not([open])').count()
    rec('unchanged-services-collapsed',closed_details>=1,closed_details)
    rec('edit-actions-six',page.locator('[data-edit-stage]').count()==6,page.locator('[data-edit-stage]').count())
    page.screenshot(path=str(ROOT/'qa/confirmation-diff-first-v036.png'),full_page=True)

    # Explicit save/cancel semantics: cancel must roll back temporary change.
    removed_before=page.locator('.delta-removed').count()
    page.click('[data-edit-stage="core"]');page.wait_for_timeout(50)
    rec('edit-mode-has-cancel-save',page.locator('#cancelEditBtn').count()==1 and page.locator('#saveEditBtn').count()==1,page.locator('#wizardActions').inner_text())
    # Temporarily remove another selected service then cancel.
    page.locator('.editable-service.selected').first.click();page.wait_for_timeout(30)
    page.click('#cancelEditBtn');page.wait_for_timeout(60)
    removed_after_cancel=page.locator('.delta-removed').count()
    rec('cancel-rolls-back',removed_after_cancel==removed_before,{'before':removed_before,'afterCancel':removed_after_cancel})
    # Now edit, restore the originally removed first core service, and save.
    page.click('[data-edit-stage="core"]');page.wait_for_timeout(40)
    page.locator('.editable-service:not(.selected)').first.click();page.wait_for_timeout(30)
    page.click('#saveEditBtn');page.wait_for_timeout(60)
    rec('save-applies',page.locator('.delta-removed').count()==0,page.locator('.delta-removed').count())
    page.screenshot(path=str(ROOT/'qa/edit-save-cancel-v036.png'),full_page=True)

    # Reset remains confirmed/destructive, away from primary flow.
    # Result hides progress/reset, so go Back to addons then use reset.
    page.click('#backBtn');page.wait_for_timeout(40)
    page.click('#restartTopBtn');page.wait_for_timeout(30)
    rec('reset-dialog-confirmation',page.locator('#resetDialog').is_visible(),page.locator('#resetDialog').is_visible())
    page.click('#resetCancelBtn');page.wait_for_timeout(20)
    rec('reset-cancel-keeps-state',page.locator('#progressTitle').inner_text()=='Шаг настройки 6 из 6',page.locator('#progressTitle').inner_text())
    page.close()

    # Small custom path / premium-only benchmark edge wording.
    page=b.new_page(viewport={'width':390,'height':844})
    page.goto(BASE,wait_until='domcontentloaded',timeout=10000);page.wait_for_timeout(70)
    page.click('[data-preset="custom"]');page.wait_for_timeout(50)
    # advance to wellness with nothing selected
    for _ in range(4): page.click('#nextBtn');page.wait_for_timeout(25)
    page.locator('[data-service]').first.click();page.wait_for_timeout(30)
    for _ in range(2): page.click('#nextBtn');page.wait_for_timeout(30)
    econ2=page.locator('.economics-card').inner_text()
    rec('premium-only-still-labeled-benchmark','Пакетный ориентир' in econ2 and 'не расчёт стоимости каждой выбранной услуги' in econ2.lower(),econ2)
    rec('custom-result-source-zero','Собрано с нуля' in page.locator('.confirmation-source').inner_text(),page.locator('.confirmation-source').inner_text())
    page.screenshot(path=str(ROOT/'qa/custom-from-zero-mobile-v036.png'),full_page=True)
    page.close()

    # Entry capture.
    page=b.new_page(viewport={'width':1920,'height':1080});page.goto(BASE,wait_until='domcontentloaded',timeout=10000);page.wait_for_timeout(80);page.screenshot(path=str(ROOT/'qa/package-board-desktop-1920-v036.png'),full_page=True);page.close()
    b.close()

report={'passed':all(r['passed'] for r in results),'passedCases':sum(r['passed'] for r in results),'failedCases':sum(not r['passed'] for r in results),'results':results}
(ROOT/'qa/BROWSER_QA_V036.json').write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print(json.dumps({'passed':report['passed'],'passedCases':report['passedCases'],'failedCases':report['failedCases'],'failures':[r for r in results if not r['passed']]},ensure_ascii=False,indent=2))
