from pathlib import Path
from datetime import datetime, timezone
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread
from functools import partial
from bs4 import BeautifulSoup
from playwright.sync_api import sync_playwright
import hashlib, json, platform

ROOT = Path(__file__).resolve().parents[4]
LEGACY = ROOT / 'outputs/rita-journey-20261002/legacy'
STAGE = LEGACY / 'staged'
EVIDENCE = Path(__file__).resolve().parent
PORTABLE_HOST = '127.0.0.1'
BASE_PATH = '/index.html'
blocked = []
responses = []


def guard(route):
    request = route.request
    if request.method not in ('GET', 'HEAD'):
        blocked.append({'method': request.method, 'url': request.url})
        route.abort()
    else:
        route.continue_()


def log_response(response):
    if response.status >= 400:
        responses.append({'status': response.status, 'url': response.url, 'method': response.request.method})


def expected_child_titles():
    index = BeautifulSoup((STAGE / 'index.html').read_text(), 'html.parser')
    mapping = {}
    for frame in index.select('iframe'):
        source = frame.get('src')
        child = BeautifulSoup((STAGE / source).read_text(), 'html.parser')
        mapping[source] = child.title.get_text(strip=True) if child.title else ''
    return mapping


baseline = json.loads((EVIDENCE / 'child-files-before.json').read_text())
titles = expected_child_titles()
assert len(titles) == 29
handler = partial(SimpleHTTPRequestHandler, directory=str(STAGE))
server = ThreadingHTTPServer((PORTABLE_HOST, 0), handler)
server_thread = Thread(target=server.serve_forever, daemon=True)
server_thread.start()
base_url = f'http://{PORTABLE_HOST}:{server.server_port}'
result = {
    'url': base_url + BASE_PATH,
    'checkedAt': datetime.now(timezone.utc).isoformat(),
    'purpose': 'Headless local staging verification. Non-GET/HEAD requests were aborted.',
    'environment': {'platform': platform.platform(), 'desktopViewport': {'width': 1440, 'height': 1000}, 'customerViewport': {'width': 390, 'height': 844}, 'deviceScaleFactor': 1},
}

try:
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True, executable_path='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome')
        context = browser.new_context(viewport={'width': 1440, 'height': 1000}, device_scale_factor=1)
        context.route('**/*', guard)
        page = context.new_page()
        page.on('response', log_response)
        page.set_default_timeout(15000)
        response = page.goto(result['url'], wait_until='domcontentloaded', timeout=45000)
        result['httpStatus'] = response.status if response else None
        result['dpr'] = page.evaluate('devicePixelRatio')
        page.wait_for_function("document.body && document.body.getAttribute('data-legacy-comment-master') === 'komplex-rita-funnel-20260926-master'", timeout=20000)
        result['masterId'] = page.locator('body').get_attribute('data-legacy-comment-master')

        page.locator('iframe').evaluate_all("frames => frames.forEach(frame => { for (let parent=frame.parentElement; parent; parent=parent.parentElement) if (parent.tagName==='DETAILS') parent.open=true; frame.loading='eager'; })")
        page.wait_for_function("""expected => {
          const frames = Array.from(document.querySelectorAll('iframe'));
          if (frames.length !== 29) return false;
          return frames.every(frame => {
            try {
              const target = new URL(frame.getAttribute('src'), document.baseURI);
              const doc = frame.contentDocument;
              const actual = doc && new URL(doc.URL);
              return !!doc && !!doc.body && doc.readyState === 'complete' && actual.origin === location.origin && actual.pathname === target.pathname && actual.search === target.search && doc.title === expected[frame.getAttribute('src')];
            } catch (_) { return false; }
          });
        }""", arg=titles, timeout=60000)
        result['allActualDocumentsReady'] = True
        page.wait_for_function("Array.from(document.querySelectorAll('iframe')).every(frame => frame.contentDocument && frame.contentDocument.getElementById('comment-html-root') && frame.contentDocument.getElementById('comment-html-root').shadowRoot && frame.contentDocument.getElementById('comment-html-root').shadowRoot.querySelector('[data-legacy-suppression]'))", timeout=30000)
        result['allInnerRootsReady'] = True

        result['frames'] = page.locator('iframe').evaluate_all("""frames => frames.map((frame,index) => {
          const doc = frame.contentDocument;
          const root = doc.getElementById('comment-html-root');
          const documentMarker = doc.querySelector('style[data-legacy-suppression=\"komplex-rita-funnel-20260926-master\"]');
          const shadowMarker = root.shadowRoot.querySelector('[data-legacy-suppression=\"komplex-rita-funnel-20260926-master\"]');
          const style = doc.defaultView.getComputedStyle(root);
          return {index,src:frame.getAttribute('src'),actualUrl:doc.URL,documentTitle:doc.title,readyState:doc.readyState,hasBody:!!doc.body,hasRoot:!!root,documentMarker:!!documentMarker,shadowMarker:!!shadowMarker,rootDisplay:style.display,rootVisibility:style.visibility,rootPointerEvents:style.pointerEvents,fullySuppressed:!!documentMarker&&!!shadowMarker&&style.display==='none'&&style.visibility==='hidden'&&style.pointerEvents==='none'};
        })""")
        result['frameSummary'] = {
            'expected': 29,
            'actualFinalDocuments': sum(1 for frame in result['frames'] if frame['readyState'] == 'complete' and frame['hasBody']),
            'roots': sum(1 for frame in result['frames'] if frame['hasRoot']),
            'documentMarkers': sum(1 for frame in result['frames'] if frame['documentMarker']),
            'shadowMarkers': sum(1 for frame in result['frames'] if frame['shadowMarker']),
            'fullySuppressed': sum(1 for frame in result['frames'] if frame['fullySuppressed']),
        }
        assert result['frameSummary']['actualFinalDocuments'] == 29
        assert result['frameSummary']['roots'] == 29
        assert result['frameSummary']['fullySuppressed'] == 29

        # Keep the outer toolbar on-screen at the header, then repeat at a deep critique target.
        toolbar = page.locator('#comment-html-root').locator('div.toolbar')
        state = toolbar.evaluate("element => {const root=element.getRootNode().host;const r=element.getBoundingClientRect();const s=getComputedStyle(element);return {pressed:root.shadowRoot.querySelector('.toggle')?.getAttribute('aria-pressed'),visible:r.width>0&&r.height>0&&s.display!=='none'&&s.visibility!=='hidden'}}")
        if not state['visible']:
            page.keyboard.press('Control+Space')
        page.wait_for_timeout(150)
        result['outerAtHeader'] = toolbar.evaluate("element => {const r=element.getBoundingClientRect(),s=getComputedStyle(element);return {scrollY,position:s.position,rect:{x:r.x,y:r.y,width:r.width,height:r.height},withinViewport:r.top>=0&&r.bottom<=innerHeight&&r.left>=0&&r.right<=innerWidth}}")
        screenshot_header = EVIDENCE / 'staged-outer-header-1440x1000-dpr1-y0.png'
        page.screenshot(path=str(screenshot_header), full_page=False)

        page.locator('#legacy-kind-ad').scroll_into_view_if_needed()
        page.wait_for_timeout(200)
        result['outerAtCritique'] = toolbar.evaluate("element => {const r=element.getBoundingClientRect(),s=getComputedStyle(element);return {scrollY,position:s.position,rect:{x:r.x,y:r.y,width:r.width,height:r.height},withinViewport:r.top>=0&&r.bottom<=innerHeight&&r.left>=0&&r.right<=innerWidth}}")
        screenshot_critique = EVIDENCE / f"staged-outer-critique-1440x1000-dpr1-y{result['outerAtCritique']['scrollY']}.png"
        page.screenshot(path=str(screenshot_critique), full_page=False)
        assert result['outerAtHeader']['position'] == 'fixed' and result['outerAtHeader']['withinViewport']
        assert result['outerAtCritique']['position'] == 'fixed' and result['outerAtCritique']['withinViewport']

        first = page.locator('iframe').first
        first.content_frame.locator('body').press('Control+Space')
        page.wait_for_timeout(250)
        result['innerShortcutAfterAttempt'] = first.evaluate("frame => {const win=frame.contentWindow,root=win.document.getElementById('comment-html-root'),toggle=root.shadowRoot.querySelector('.toolbar .toggle'),style=win.getComputedStyle(root);return {pressed:toggle.getAttribute('aria-pressed'),display:style.display,visibility:style.visibility,pointerEvents:style.pointerEvents}}")
        assert result['innerShortcutAfterAttempt'] == {'pressed':'false','display':'none','visibility':'hidden','pointerEvents':'none'}

        standalone = context.new_page()
        standalone.set_viewport_size({'width': 390, 'height': 844})
        standalone.on('response', log_response)
        standalone.goto(base_url + '/persona.html', wait_until='domcontentloaded', timeout=30000)
        standalone.wait_for_function("document.getElementById('comment-html-root') && document.getElementById('comment-html-root').shadowRoot", timeout=20000)
        standalone.keyboard.press('Control+Space')
        standalone.wait_for_timeout(150)
        child_toolbar = standalone.locator('#comment-html-root').locator('div.toolbar')
        result['standalonePersona'] = child_toolbar.evaluate("element => {const r=element.getBoundingClientRect(),s=getComputedStyle(element);return {viewport:{width:innerWidth,height:innerHeight},dpr:devicePixelRatio,scrollY,position:s.position,rect:{x:r.x,y:r.y,width:r.width,height:r.height},visible:r.width>0&&r.height>0&&s.display!=='none'&&s.visibility!=='hidden'}}")
        screenshot_persona = EVIDENCE / 'standalone-persona-390x844-dpr1-y0.png'
        standalone.screenshot(path=str(screenshot_persona), full_page=False)
        assert result['standalonePersona']['visible']
        standalone.close()
        page.close()
        context.close()
        browser.close()

    child_results = {}
    for relative, expected in baseline['children'].items():
        raw = (STAGE / relative).read_bytes()
        actual = hashlib.sha256(raw).hexdigest()
        child_results[relative] = {'sha256': actual, 'bytes': len(raw), 'unchanged': actual == expected['sha256']}
    result['childFileSummary'] = {'total':len(child_results),'unchanged':sum(1 for row in child_results.values() if row['unchanged']),'allUnchanged':all(row['unchanged'] for row in child_results.values())}
    result['screenshots'] = [
        {'path':str(screenshot_header.relative_to(ROOT)),'viewport':'1440x1000','dpr':1,'scrollY':result['outerAtHeader']['scrollY']},
        {'path':str(screenshot_critique.relative_to(ROOT)),'viewport':'1440x1000','dpr':1,'scrollY':result['outerAtCritique']['scrollY']},
        {'path':str(screenshot_persona.relative_to(ROOT)),'viewport':'390x844','dpr':1,'scrollY':result['standalonePersona']['scrollY']},
    ]
    result['blockedNonReadRequests'] = blocked
    result['failedHttpResponses'] = responses
    assert result['childFileSummary']['allUnchanged']
    assert not blocked, f'non-read requests were attempted and aborted: {blocked}'
    (EVIDENCE / 'staged-browser-metadata.json').write_text(json.dumps(result, ensure_ascii=False, indent=2))
    print(json.dumps({key:value for key,value in result.items() if key != 'frames'},ensure_ascii=False,indent=2))
finally:
    server.shutdown()
    server.server_close()
    server_thread.join(timeout=5)
