import base64

import pytest

import chat


def test_harvest_and_live_poll_strip_the_reasoning_label_from_the_turn_text():
    """The reasoning toggle+region live inside the assistant message node, but a
    SEPARATE sibling from the turn's own text — so reading the attached
    `[data-message-author-role="assistant"]` node directly excludes the reasoning
    widget structurally, no button-pruning needed. A cloned/detached node has no
    layout, so `innerText` on it silently degrades to `textContent` and drops every
    element-boundary newline (the regression this guards): both scripts must read
    the live node in place and must never clone it first."""
    assert 'data-message-author-role="assistant"' in chat.LIVE_STATE
    assert 'data-message-author-role="assistant"' in chat.HARVEST
    assert "cloneNode" not in chat.LIVE_STATE
    assert "cloneNode" not in chat.HARVEST


def test_attachment_button_scripts_share_filename_pill_predicate():
    assert "^download\\s+.+" in chat.DOWNLOAD_BUTTONS
    assert "p.not-prose.truncate" in chat.DOWNLOAD_BUTTONS
    assert "const byLabel = new Map()" in chat.DOWNLOAD_BUTTONS
    assert "byLabel.set(gptbridgeDownloadKey(text), button)" in chat.DOWNLOAD_BUTTONS
    assert "gptbridgeDownloadKey(arguments[2] || '')" in chat.CLICK_DOWNLOAD
    for script in (chat.HARVEST, chat.CLICK_DOWNLOAD, chat.HARVEST_ALL):
        assert script.startswith(chat.DOWNLOAD_BUTTONS)
        assert "gptbridgeDownloadButtons(last)" in script


class HarvestBrowser:
    def __init__(self, data, blobs):
        self.data = data
        self.blobs = iter(blobs)

    def sync_script(self, script, args):
        assert script is chat.HARVEST
        return self.data

    def script(self, script, args):
        assert script is chat.FETCH
        return next(self.blobs)


def data_url(payload: bytes) -> str:
    return "data:application/octet-stream;base64," + base64.b64encode(payload).decode()


def test_harvest_downloads_generated_files_and_images(tmp_path):
    browser = HarvestBrowser(
        {
            "text": "done",
            "url": "https://chatgpt.com/c/1",
            "images": ["https://example.test/image"],
            "files": [{"src": "https://chatgpt.com/backend-api/files/1", "name": "theme.zip"}],
        },
        [
            {"type": "image/png", "data": data_url(b"image")},
            {"type": "application/zip", "data": data_url(b"archive")},
        ],
    )

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

    assert reply.text == "done"
    assert reply.url == "https://chatgpt.com/c/1"
    assert [path.name for path in reply.images] == ["stamp-prompt.png"]
    assert [path.name for path in reply.files] == ["theme.zip"]
    assert reply.images[0].read_bytes() == b"image"
    assert reply.files[0].read_bytes() == b"archive"


def test_harvest_uses_content_disposition_filename(tmp_path):
    browser = HarvestBrowser(
        {"files": [{"src": "https://chatgpt.com/backend-api/files/1", "name": "Download"}]},
        [{
            "disposition": "attachment; filename*=UTF-8''my%20theme.zip",
            "data": data_url(b"archive"),
        }],
    )

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

    assert [path.name for path in reply.files] == ["my theme.zip"]


def test_harvest_rejects_existing_destination(tmp_path):
    (tmp_path / "theme.zip").write_bytes(b"existing")
    browser = HarvestBrowser(
        {"files": [{"src": "https://example.test/theme.zip", "name": "theme.zip"}]},
        [{"data": data_url(b"replacement")}],
    )

    with pytest.raises(chat.ChatError, match="refusing to overwrite"):
        chat.harvest(browser, tmp_path, "stamp", "prompt")

    assert (tmp_path / "theme.zip").read_bytes() == b"existing"


def test_harvest_rejects_failed_file_download(tmp_path):
    browser = HarvestBrowser(
        {"files": [{"src": "https://example.test/theme.zip", "name": "theme.zip"}]},
        [{"error": "http 403"}],
    )

    with pytest.raises(chat.ChatError, match="could not download file 1"):
        chat.harvest(browser, tmp_path, "stamp", "prompt")


def test_harvest_clicks_download_inside_generated_file_drawer(tmp_path):
    downloads = tmp_path / "downloads"
    output = tmp_path / "output"

    class DrawerBrowser:
        download_dir = downloads

        def __init__(self):
            self.drawer_attempts = 0

        def sync_script(self, script, args):
            if script is chat.HARVEST:
                return {"downloads": ["Download generated-theme.zip"]}
            if script is chat.CLICK_DOWNLOAD:
                assert args == [chat.TURN, chat.USER, "Download generated-theme.zip"]
                return True
            assert script is chat.CLICK_DRAWER_DOWNLOAD
            assert args == ["generated-theme.zip"]
            self.drawer_attempts += 1
            if self.drawer_attempts == 1:
                return "ambiguous-button"
            downloads.mkdir(parents=True, exist_ok=True)
            (downloads / "generated-theme.zip").write_bytes(b"archive")
            return "clicked"

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

    assert browser.drawer_attempts == 2
    assert [path.name for path in reply.files] == ["generated-theme.zip"]
    assert reply.files[0].read_bytes() == b"archive"
    assert not (downloads / "generated-theme.zip").exists()


def test_harvest_clicks_generated_file_button(tmp_path):
    downloads = tmp_path / "downloads"
    output = tmp_path / "output"

    class ButtonBrowser:
        download_dir = downloads

        def sync_script(self, script, args):
            if script is chat.HARVEST:
                return {"downloads": ["generated-theme.zip"]}
            if script is chat.CLICK_DRAWER_DOWNLOAD:
                return "absent"
            assert script is chat.CLICK_DOWNLOAD
            assert args == [chat.TURN, chat.USER, "generated-theme.zip"]
            downloads.mkdir(parents=True, exist_ok=True)
            (downloads / "generated-theme.zip").write_bytes(b"archive")
            return True

    reply = chat.harvest(ButtonBrowser(), output, "stamp", "prompt")

    assert [path.name for path in reply.files] == ["generated-theme.zip"]
    assert reply.files[0].read_bytes() == b"archive"
    assert not (downloads / "generated-theme.zip").exists()
