"""Copy-button/clipboard reply text (chat.copy_reply_text, wired into chat.harvest).

`innerText` returns the RENDERED markdown, not the source: a `# heading` loses its
'#', a `* item` list marker disappears into a bare <li>, `*emphasis*` loses both
'*'. ChatGPT's own per-message Copy button writes the ORIGINAL markdown to the
clipboard, so that — not HARVEST's innerText `text` field — is the authoritative
text source for a completed reply. HARVEST still owns image/file/download
discovery, unaffected by any of this.
"""

import chat
from fake_browser import FakeMarionette

# What innerText destroys: leading '#', leading '* ' + indent, and the asterisks
# around 'glob' all vanish once rendered — the whole point of the fix.
MARKDOWN_SOURCE = "# WordPress\n\n/.verify-*/\n/**\n\n* Core Types\n  */\n*glob*\n"
RENDERED_INNERTEXT = "WordPress\n\nCore Types"


class CopyHarvestBrowser:
    """Models HARVEST + the copy button + the clipboard for one `chat.harvest()` call."""

    def __init__(self, harvest_data, *, button_present=True, clipboard_text="",
                clipboard_fails=False):
        self.harvest_data = harvest_data
        self.button_present = button_present
        self.clipboard_text = clipboard_text
        self.clipboard_fails = clipboard_fails
        self.clicked: list[str] = []

    def sync_script(self, source, args=None):
        if source == chat.HARVEST:
            return self.harvest_data
        if source == chat.COPY_BUTTON:
            return self.button_present
        raise AssertionError(f"unscripted call: {source[:60]!r}")

    def js_click(self, selector):
        self.clicked.append(selector)
        return True

    def script(self, source, args=None):
        if source == chat.CLIPBOARD_READ:
            if self.clipboard_fails:
                raise RuntimeError("clipboard permission denied")
            return self.clipboard_text
        raise AssertionError(f"unscripted async call: {source[:60]!r}")


def test_harvest_returns_the_clipboard_markdown_verbatim_not_rendered_innertext(tmp_path):
    browser = CopyHarvestBrowser(
        {"text": RENDERED_INNERTEXT, "url": "https://chatgpt.com/c/1"},
        clipboard_text=MARKDOWN_SOURCE,
    )

    reply = chat.harvest(browser, tmp_path, "stamp", "prompt")

    assert reply.text == MARKDOWN_SOURCE
    assert reply.text.startswith("#")
    assert "\n* Core Types" in reply.text
    assert "*glob*" in reply.text
    assert browser.clicked == ["[data-gptbridge]"]


def test_harvest_falls_back_to_innertext_when_the_copy_button_is_absent(tmp_path, capsys):
    browser = CopyHarvestBrowser(
        {"text": RENDERED_INNERTEXT, "url": "https://chatgpt.com/c/1"},
        button_present=False,
    )

    reply = chat.harvest(browser, tmp_path, "stamp", "prompt")

    assert reply.text == RENDERED_INNERTEXT
    assert not browser.clicked
    assert "clipboard" in capsys.readouterr().err


def test_harvest_falls_back_when_the_clipboard_read_fails(tmp_path, capsys):
    browser = CopyHarvestBrowser(
        {"text": RENDERED_INNERTEXT, "url": "https://chatgpt.com/c/1"},
        clipboard_fails=True,
    )

    reply = chat.harvest(browser, tmp_path, "stamp", "prompt")

    assert reply.text == RENDERED_INNERTEXT
    assert "clipboard" in capsys.readouterr().err


def test_harvest_falls_back_when_the_clipboard_reads_empty(tmp_path, capsys):
    browser = CopyHarvestBrowser(
        {"text": RENDERED_INNERTEXT, "url": "https://chatgpt.com/c/1"},
        clipboard_text="",
    )

    reply = chat.harvest(browser, tmp_path, "stamp", "prompt")

    assert reply.text == RENDERED_INNERTEXT
    assert "clipboard" in capsys.readouterr().err


def test_ask_on_reply_and_done_event_carry_the_clipboard_text_not_innertext(tmp_path):
    """FakeMarionette models the daemon's real browser seat, driven the same way
    solwebd's `/v1/ask` handler drives it (`chat.ask_on`). Both the returned Reply
    and the `done` event's `reply.text` — what solwebd's JSON response and
    ask-gpt's `--json`/RunLog ultimately forward — must be the clipboard text."""
    marionette = FakeMarionette([
        {"blocks": [], "text": RENDERED_INNERTEXT, "clip": MARKDOWN_SOURCE},
    ])
    events: list[dict] = []

    reply = chat.ask_on(marionette, "hello", out_dir=tmp_path, stamp="s",
                        on_event=events.append)

    assert reply.text == MARKDOWN_SOURCE
    done = next(e for e in events if e["type"] == "done")
    assert done["reply"]["text"] == MARKDOWN_SOURCE


def test_ask_on_falls_back_to_innertext_when_the_fake_seat_has_no_copy_button(tmp_path):
    marionette = FakeMarionette([
        {"blocks": [], "text": RENDERED_INNERTEXT, "clip": MARKDOWN_SOURCE},
    ])
    marionette.copy_button_present = False

    reply = chat.ask_on(marionette, "hello", out_dir=tmp_path, stamp="s")

    assert reply.text == RENDERED_INNERTEXT
