# -*- coding: utf-8 -*-
"""
STEP105-09 V2 Body Input - Rendered Preview Copy

역할:
- V2 전용 본문 입력 모듈.
- V1 publish_worker.py를 수정하지 않는다.
- clipboard_html을 직접 ClipboardItem으로 넣지 않고,
  브라우저 preview 페이지에서 렌더링된 결과를 복사한 뒤 네이버 에디터에 붙여넣는다.

핵심 정책:
- Rendered Preview Copy 우선
- plain_text fallback은 최후의 안전장치
- 네이버 CDN / Cafe24 CDN 이미지가 브라우저에서 렌더링된 상태로 복사되어야 한다.
"""

import re
import tempfile
from pathlib import Path


class V2BodyInput:
    def __init__(self, verbose=True):
        self.verbose = verbose

    def input_body(self, page, write_frame, html, fallback_text=None, logger=None, session=None):
        def _run():
            return self._input_body_impl(
                page=page,
                write_frame=write_frame,
                html=html,
                fallback_text=fallback_text,
            )

        if session and logger:
            with session.step(
                "body.input.rendered_preview_copy",
                logger=logger,
                meta={
                    "html_len": len(str(html or "")),
                    "fallback_len": len(str(fallback_text or "")),
                    "mode": "rendered_preview_copy",
                },
            ):
                return _run()

        return _run()

    def _input_body_impl(self, page, write_frame, html, fallback_text=None):
        html = str(html or "").strip()
        fallback_text = str(fallback_text or "").strip()

        if not html and not fallback_text:
            raise ValueError("본문 HTML과 fallback_text가 모두 비어 있습니다.")

        print("[V2 BODY INPUT START]", f"mode=rendered_preview_copy html_len={len(html)} fallback_len={len(fallback_text)}")

        if html:
            copy_result = self._copy_rendered_preview(page=page, html=html)
            print("[V2 BODY RENDERED COPY]", copy_result)

            clicked = self._click_body_area(page, write_frame)
            if not clicked:
                raise RuntimeError("본문 영역 클릭 실패")

            page.keyboard.press("Control+V")
            page.wait_for_timeout(3000)

            verify = self.verify_body(write_frame)
            print("[V2 BODY VERIFY RENDERED HTML]", verify)

            if verify.get("ok"):
                return {
                    "status": "success",
                    "mode": "rendered_preview_copy",
                    "html_len": len(html),
                    "copy": copy_result,
                    "verify": verify,
                }

            print("[V2 BODY RENDERED VERIFY FAILED] fallback 시도")

        if fallback_text:
            clicked = self._click_body_area(page, write_frame)
            if not clicked:
                raise RuntimeError("본문 영역 클릭 실패")

            self._set_clipboard_text(page, fallback_text)
            page.wait_for_timeout(500)
            page.keyboard.press("Control+V")
            page.wait_for_timeout(1500)

            verify = self.verify_body(write_frame)
            print("[V2 BODY VERIFY TEXT]", verify)

            if verify.get("ok"):
                return {
                    "status": "success",
                    "mode": "fallback_text",
                    "text_len": len(fallback_text),
                    "verify": verify,
                }

        raise RuntimeError("본문 입력 검증 실패")

    def _copy_rendered_preview(self, page, html):
        """
        현재 page와 같은 browser context 안에서 preview page를 열고,
        HTML을 렌더링한 뒤 preview-root 영역을 전체 선택하여 복사한다.
        """
        context = page.context

        preview_html = self._wrap_preview_html(html)
        tmp_path = self._write_temp_preview(preview_html)

        preview_page = None

        try:
            preview_page = context.new_page()
            preview_url = Path(tmp_path).as_uri()

            print("[V2 BODY PREVIEW OPEN]", preview_url)

            preview_page.goto(
                preview_url,
                wait_until="load",
                timeout=60000,
            )
            preview_page.wait_for_timeout(1500)

            # 이미지 로딩을 조금 기다린다.
            self._wait_images(preview_page, timeout_ms=8000)

            root = preview_page.locator("#v2-preview-root").first
            root.click(force=True, timeout=5000)
            preview_page.wait_for_timeout(300)

            # DOM Range 기반으로 preview-root 전체 선택
            preview_page.evaluate(
                """() => {
                    const root = document.querySelector('#v2-preview-root');
                    const range = document.createRange();
                    range.selectNodeContents(root);
                    const sel = window.getSelection();
                    sel.removeAllRanges();
                    sel.addRange(range);
                }"""
            )

            preview_page.wait_for_timeout(300)
            preview_page.keyboard.press("Control+C")
            preview_page.wait_for_timeout(800)

            stats = preview_page.evaluate(
                """() => {
                    const root = document.querySelector('#v2-preview-root');
                    const text = (root && root.innerText) ? root.innerText.trim() : '';
                    const imgs = root ? root.querySelectorAll('img') : [];
                    const cafe24Imgs = Array.from(imgs).filter(img => (img.src || '').includes('cafe24')).length;
                    const naverImgs = Array.from(imgs).filter(img => (img.src || '').includes('pstatic') || (img.src || '').includes('naver')).length;
                    return {
                        text_len: text.length,
                        image_count: imgs.length,
                        cafe24_image_count: cafe24Imgs,
                        naver_image_count: naverImgs,
                        sample_text: text.slice(0, 200)
                    };
                }"""
            )

            return {
                "status": "success",
                "tmp_path": str(tmp_path),
                "preview_url": preview_url,
                "stats": stats,
            }

        finally:
            if preview_page:
                try:
                    preview_page.close()
                except Exception:
                    pass

    def _wrap_preview_html(self, html):
        return f"""<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>V2 Rendered Preview</title>
<style>
body {{
    margin: 0;
    padding: 24px;
    background: #ffffff;
    font-family: -apple-system, BlinkMacSystemFont, "Malgun Gothic", "맑은 고딕", Arial, sans-serif;
    color: #222;
}}
#v2-preview-root {{
    width: 760px;
    margin: 0 auto;
    background: #fff;
}}
#v2-preview-root img {{
    max-width: 100%;
    height: auto;
}}
table {{
    border-collapse: collapse;
}}
td, th {{
    border: 1px solid #ddd;
    padding: 6px 8px;
}}
</style>
</head>
<body>
<div id="v2-preview-root">
{html}
</div>
</body>
</html>"""

    def _write_temp_preview(self, html):
        tmp_dir = Path(tempfile.gettempdir()) / "blog_v2_preview"
        tmp_dir.mkdir(parents=True, exist_ok=True)

        path = tmp_dir / "v2_rendered_preview.html"
        path.write_text(html, encoding="utf-8")

        return path

    def _wait_images(self, preview_page, timeout_ms=8000):
        try:
            preview_page.evaluate(
                """async (timeoutMs) => {
                    const imgs = Array.from(document.querySelectorAll('#v2-preview-root img'));
                    const waitOne = img => new Promise(resolve => {
                        if (img.complete) return resolve(true);
                        const done = () => resolve(true);
                        img.addEventListener('load', done, {once: true});
                        img.addEventListener('error', done, {once: true});
                    });
                    await Promise.race([
                        Promise.all(imgs.map(waitOne)),
                        new Promise(resolve => setTimeout(resolve, timeoutMs))
                    ]);
                }""",
                timeout_ms,
            )
        except Exception as e:
            print("[V2 BODY IMAGE WAIT WARN]", str(e))

    def _click_body_area(self, page, write_frame):
        selectors = [
            ".se-section-text .se-module-text",
            ".se-component.se-text",
            ".se-section-text",
            "article.se-components-wrap",
        ]

        for selector in selectors:
            try:
                locator = write_frame.locator(selector)

                if locator.count() <= 0:
                    continue

                target = locator.first
                target.click(force=True, timeout=5000)
                page.wait_for_timeout(500)

                if self.verbose:
                    print("[V2 BODY CLICK]", selector)

                return True

            except Exception as e:
                if self.verbose:
                    print("[V2 BODY CLICK FAIL]", selector, str(e))

        return False

    def _set_clipboard_text(self, page, text):
        page.evaluate(
            """async (text) => {
                await navigator.clipboard.writeText(text);
            }""",
            text,
        )

    def verify_body(self, write_frame):
        result = {
            "ok": False,
            "text_len": 0,
            "image_count": 0,
            "component_count": 0,
            "sample_text": "",
        }

        try:
            body = write_frame.locator(".se-component.se-text, .se-components-wrap").first
            text = body.inner_text(timeout=5000)
            text = str(text or "").strip()
            cleaned = re.sub(r"\\s+", " ", text)

            result["text_len"] = len(cleaned)
            result["sample_text"] = cleaned[:200]
        except Exception:
            pass

        try:
            result["image_count"] = write_frame.locator("img").count()
        except Exception:
            pass

        try:
            result["component_count"] = write_frame.locator(".se-component").count()
        except Exception:
            pass

        if result["text_len"] >= 30 or result["image_count"] > 0 or result["component_count"] >= 2:
            result["ok"] = True

        return result


def input_body(page, write_frame, html, fallback_text=None, logger=None, session=None, verbose=True):
    worker = V2BodyInput(verbose=verbose)
    return worker.input_body(
        page=page,
        write_frame=write_frame,
        html=html,
        fallback_text=fallback_text,
        logger=logger,
        session=session,
    )
