import json
import subprocess
import textwrap
import unittest
from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parents[1]
PREPARE_SCRIPT_PATH = (
    REPO_ROOT.parent / "skill_lib" / "skills" / "zhihu-write" / "scripts" /
    "prepare_article_editor.js"
)
FILL_SCRIPT_PATH = (
    REPO_ROOT.parent / "skill_lib" / "skills" / "zhihu-write" / "scripts" /
    "fill_article_draft.js"
)


def run_browser_script(script_path: Path, *, args: dict, body_text: str, selectors: dict[str, list[dict]]) -> dict:
    node_script = textwrap.dedent(
        f"""
        import fs from 'node:fs';
        import vm from 'node:vm';

        const scriptPath = {json.dumps(str(script_path))};
        const args = {json.dumps(args, ensure_ascii=False)};
        const selectorMap = {json.dumps(selectors, ensure_ascii=False)};
        const bodyText = {json.dumps(body_text, ensure_ascii=False)};
        const source = fs.readFileSync(scriptPath, 'utf8');

        function createNode(spec) {{
          const attrs = spec?.attrs || {{}};
          const node = {{
            tagName: String(spec?.tagName || 'DIV').toUpperCase(),
            textContent: String(spec?.textContent ?? ''),
            innerText: String(spec?.innerText ?? spec?.textContent ?? ''),
            innerHTML: String(spec?.innerHTML ?? spec?.textContent ?? ''),
            value: String(spec?.value ?? ''),
            children: [],
            focused: false,
            clicked: false,
            appendChild(child) {{
              this.children.push(child);
              return child;
            }},
            focus() {{
              this.focused = true;
            }},
            click() {{
              this.clicked = true;
            }},
            dispatchEvent() {{
              return true;
            }},
            getAttribute(name) {{
              return Object.prototype.hasOwnProperty.call(attrs, name) ? attrs[name] : null;
            }},
            querySelector() {{
              return null;
            }},
            querySelectorAll() {{
              return [];
            }},
            getBoundingClientRect() {{
              return {{
                width: spec?.visible === false ? 0 : 100,
                height: spec?.visible === false ? 0 : 20,
              }};
            }},
          }};
          return node;
        }}

        const created = new Map();

        function createNodeList(selector) {{
          const specs = selectorMap[selector] || [];
          return specs.map((spec, index) => {{
            const key = `${{selector}}#${{index}}`;
            if (!created.has(key)) {{
              created.set(key, createNode(spec));
            }}
            return created.get(key);
          }});
        }}

        const bodyNode = createNode({{ tagName: 'body', textContent: bodyText, innerText: bodyText }});
        const context = {{
          args,
          location: {{ href: 'https://zhuanlan.zhihu.com/write' }},
          document: {{
            body: bodyNode,
            createElement(tagName) {{
              return createNode({{ tagName }});
            }},
            createTextNode(text) {{
              return createNode({{ tagName: '#text', textContent: text, innerText: text }});
            }},
            querySelector(selector) {{
              if (selector === 'body') {{
                return bodyNode;
              }}
              return createNodeList(selector)[0] || null;
            }},
            querySelectorAll(selector) {{
              return createNodeList(selector);
            }},
          }},
          Event: class Event {{
            constructor(type, init = {{}}) {{
              this.type = type;
              this.bubbles = !!init.bubbles;
              this.composed = !!init.composed;
            }}
          }},
          console,
          JSON,
          Math,
          Number,
          Object,
          RegExp,
          Set,
          String,
          Array,
          Error,
        }};

        try {{
          const result = vm.runInNewContext(`(function(){{\\n${{source}}\\n}})()`, context);
          process.stdout.write(JSON.stringify({{ ok: true, result, created: Object.fromEntries(created) }}));
        }} catch (error) {{
          process.stdout.write(JSON.stringify({{
            ok: false,
            error: String(error && error.message ? error.message : error),
          }}));
          process.exitCode = 1;
        }}
        """
    )
    completed = subprocess.run(
        ["node", "--input-type=module", "-e", node_script],
        check=False,
        capture_output=True,
        text=True,
    )
    payload = json.loads(completed.stdout)
    if completed.returncode != 0:
        raise AssertionError(payload["error"])
    return payload


class SkillScriptZhihuWriteTest(unittest.TestCase):
    def test_prepare_article_editor_accepts_role_textbox_title_and_generic_body_editor(self):
        payload = run_browser_script(
            PREPARE_SCRIPT_PATH,
            args={"desired_mode": "draft"},
            body_text="写文章 发布",
            selectors={
                "[role='textbox'][aria-label*='标题']": [
                    {
                        "tagName": "div",
                        "attrs": {
                            "role": "textbox",
                            "aria-label": "标题",
                            "contenteditable": "true",
                        },
                    }
                ],
                "div[contenteditable='true']": [
                    {
                        "tagName": "div",
                        "attrs": {
                            "contenteditable": "true",
                            "data-placeholder": "在这里输入正文",
                        },
                    }
                ],
            },
        )

        self.assertEqual(payload["result"]["status"], "editor_ready")

    def test_fill_article_draft_accepts_role_textbox_title_and_generic_body_editor(self):
        payload = run_browser_script(
            FILL_SCRIPT_PATH,
            args={
                "title": "测试标题",
                "body": "第一段\n第二段",
                "publish_mode": "false",
            },
            body_text="写文章 发布",
            selectors={
                "[role='textbox'][aria-label*='标题']": [
                    {
                        "tagName": "div",
                        "attrs": {
                            "role": "textbox",
                            "aria-label": "标题",
                            "contenteditable": "true",
                        },
                    }
                ],
                "div[contenteditable='true']": [
                    {
                        "tagName": "div",
                        "attrs": {
                            "contenteditable": "true",
                            "data-placeholder": "在这里输入正文",
                        },
                    }
                ],
            },
        )

        self.assertEqual(payload["result"]["status"], "draft_ready")


if __name__ == "__main__":
    unittest.main()
