"""Offline A4 PDF export using the already installed Chromium and Poppler."""
import base64
import json
import subprocess
import time
import urllib.request
from pathlib import Path
import websocket
from content import OPERATIONS, SCENARIOS

ROOT=Path(__file__).resolve().parent.parent
RUN=Path('/tmp/codex/ltb1021/2026-10-06_icerti-ai2-deploy_r01/pdf-validation')
CHROME='/root/.cache/ms-playwright/chromium_headless_shell-1228/chrome-headless-shell-linux64/chrome-headless-shell'
CSS='''@page{size:A4;margin:0}*{box-sizing:border-box;letter-spacing:0}body{margin:0;color:#202020;font:11pt/1.45 "Noto Sans CJK KR",sans-serif}section{width:210mm;height:297mm;padding:14mm 16mm 20mm;position:relative;break-after:page}section:last-child{break-after:auto}header{border-bottom:1.2pt solid #333;padding-bottom:7px;font-size:9pt}h1{font-size:21pt;line-height:1.3;margin:16px 0 12px}h2{font-size:12pt;margin:14px 0 5px}p{margin:7px 0;word-break:keep-all;overflow-wrap:anywhere}.lead{border-left:3px solid #555;padding-left:10px;margin-bottom:14px}table{width:100%;border-collapse:collapse;table-layout:fixed;margin:10px 0;font-size:10.6pt}th,td{border:1px solid #999;padding:6px 8px;vertical-align:top;overflow-wrap:anywhere;word-break:keep-all}th{background:#eee}pre{padding:8px 10px;border:1px solid #888;background:#f5f5f5;font:10.5pt/1.45 "DejaVu Sans Mono",monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:7px 0}code{font-family:"DejaVu Sans Mono",monospace;font-size:.9em;overflow-wrap:anywhere}.note{border-top:1px solid #777;padding-top:10px;margin-top:12px}footer{position:absolute;left:16mm;right:16mm;bottom:8mm;border-top:1px solid #999;padding-top:5px;display:flex;justify-content:space-between;font-size:8.5pt}a{color:#222}'''

class CDP:
    def __init__(self,url):
        self.ws=websocket.create_connection(url,timeout=45,suppress_origin=True);self.n=0
    def call(self,method,params=None):
        self.n+=1
        self.ws.send(json.dumps(dict(id=self.n,method=method,params=params or {})))
        while True:
            r=json.loads(self.ws.recv())
            if r.get('id')==self.n:
                if 'error' in r:raise RuntimeError(r['error'])
                return r.get('result',{})

def document(title,pages):
    out=[]
    for i,(key,heading,body) in enumerate(pages,1):
        links=' · '.join(f'<a href="#{p[0]}">{p[0]}</a>' for p in pages)
        out.append(f'<section id="{key}"><div class="content"><header>CONTEXTRA · AI 서버2 | {title} · {links}</header><h1>{heading}</h1>{body}</div><footer><span>2026.10.06 · 설치 기준 · si 계정</span><span>{key} | {i} / {len(pages)}</span></footer></section>')
    return '<!doctype html><html lang="ko"><meta charset="utf-8"><title>'+title+'</title><style>'+CSS+'</style><body>'+''.join(out)+'</body></html>'

CHECK='''(()=>{const issues=[];for(const p of document.querySelectorAll('section')){const b=p.getBoundingClientRect(),f=p.querySelector('footer').getBoundingClientRect();if(p.querySelector('.content').getBoundingClientRect().bottom>f.top-10)issues.push(p.id+':footer overlap');for(const e of p.querySelectorAll('p,pre,td,th,h1,h2')){const r=e.getBoundingClientRect();if(r.right>b.right-45||r.left<b.left+45||e.scrollWidth>e.clientWidth+2)issues.push(p.id+':width:'+e.tagName)}for(const a of p.querySelectorAll('a'))if(!document.querySelector(a.hash))issues.push('link:'+a.hash)}return {issues,pages:document.querySelectorAll('section').length,external:performance.getEntriesByType('resource').filter(r=>/^https?:/.test(r.name)).length}})()'''

def main():
    RUN.mkdir(parents=True,exist_ok=True)
    profile=RUN/'chromium'
    (profile/'DevToolsActivePort').unlink(missing_ok=True)
    with (RUN/'chromium.log').open('w') as log:
        p=subprocess.Popen([CHROME,'--no-sandbox','--disable-gpu','--disable-background-networking','--no-first-run','--disable-dev-shm-usage','--remote-debugging-address=127.0.0.1','--remote-debugging-port=0','--user-data-dir='+str(profile),'about:blank'],stdout=log,stderr=log)
        try:
            for _ in range(100):
                if (profile/'DevToolsActivePort').exists():break
                if p.poll() is not None:raise RuntimeError('Chromium exited')
                time.sleep(.1)
            port=(profile/'DevToolsActivePort').read_text().splitlines()[0]
            targets=json.load(urllib.request.urlopen('http://127.0.0.1:'+port+'/json',timeout=5))
            c=CDP(next(t['webSocketDebuggerUrl'] for t in targets if t['type']=='page'))
            c.call('Page.enable');c.call('Network.enable');c.call('Network.setBlockedURLs',{'urls':['http://*','https://*']});c.call('Emulation.setEmulatedMedia',{'media':'print'})
            results={}
            for name,title,pages in [('01_ai2_operations_20261006','운영·복구',OPERATIONS),('02_ai2_scenarios_20261006','시나리오 설정 참고',SCENARIOS)]:
                h=ROOT/(name+'.html');h.write_text(document(title,pages))
                c.call('Page.navigate',{'url':h.as_uri()})
                for _ in range(80):
                    r=c.call('Runtime.evaluate',{'expression':'document.readyState','returnByValue':True})
                    if r['result'].get('value')=='complete':break
                    time.sleep(.1)
                c.call('Runtime.evaluate',{'expression':'document.fonts.ready.then(()=>true)','awaitPromise':True})
                result=c.call('Runtime.evaluate',{'expression':CHECK,'returnByValue':True})['result']['value']
                results[name]=result
                (RUN/'layout.json').write_text(json.dumps(results,indent=2))
                assert not result['issues'] and result['external']==0,result
                pdf=c.call('Page.printToPDF',{'preferCSSPageSize':True,'printBackground':True,'displayHeaderFooter':False,'generateTaggedPDF':True,'generateDocumentOutline':True})
                (ROOT/(name+'.pdf')).write_bytes(base64.b64decode(pdf['data']))
            c.ws.close()
            print(json.dumps(results))
        finally:
            p.terminate()
            try:p.wait(timeout=10)
            except subprocess.TimeoutExpired:p.kill();p.wait(timeout=5)

if __name__=='__main__':main()
