import os
import tempfile
import uuid

import pytest
import pytest_asyncio

from webdriver import TimeoutException
from webdriver.bidi.modules.script import ContextTarget

DOWNLOAD_END = "browsingContext.downloadEnd"


@pytest.fixture
def temp_dir():
    return tempfile.mkdtemp()


@pytest_asyncio.fixture(params=["new", "default"])
async def user_context_invariant(request, create_user_context):
    if request.param == "default":
        return "default"
    return await create_user_context()


@pytest.fixture(params=[True, False])
def is_download_allowed_invariant(request, temp_dir):
    return request.param


@pytest.fixture
def some_download_behavior(is_download_allowed_invariant, temp_dir):
    """
    Returns a download behavior matching `is_download_allowed_invariant`.
    """
    if is_download_allowed_invariant:
        return {
            "type": "allowed",
            "destinationFolder": temp_dir
        }
    return {"type": "denied"}


@pytest.fixture
def opposite_download_behavior(is_download_allowed_invariant, temp_dir):
    """
    Returns a download behavior opposite to `is_download_allowed_invariant`.
    """
    if is_download_allowed_invariant:
        return {"type": "denied"}
    return {
        "type": "allowed",
        "destinationFolder": temp_dir
    }


@pytest.fixture
def trigger_download(bidi_session, subscribe_events, wait_for_event,
        wait_for_future_safe, inline, temp_dir):
    """
    Triggers download and returns either `browsingContext.downloadEnd` event or
    None if the download was not ended (e.g. if User Agent showed file save
    dialog).
    """

    downloaded_files = []

    async def trigger_download(context):
        # Generate a unique `.txt` file name, so that downloads triggered by different
        # tests don't conflict with each other.
        filename = f"{uuid.uuid4().hex}.txt"
        page_with_download_link = inline(
            f"""<a id="download_link" href="{inline("")}" download="{filename}">download</a>""")
        await bidi_session.browsing_context.navigate(context=context["context"],
                                                     url=page_with_download_link,
                                                     wait="complete")

        await subscribe_events(events=[DOWNLOAD_END])

        on_download_end = wait_for_event(DOWNLOAD_END)
        # Trigger download by clicking the link.
        await bidi_session.script.evaluate(
            expression="download_link.click()",
            target=ContextTarget(context["context"]),
            await_promise=True,
            user_activation=True,
        )

        try:
            print("Wait for browsingContext.downloadEnd event")
            event = await wait_for_future_safe(
                on_download_end, timeout=2.0)

            # Save only file paths that are saved not in the temporary folder to remove them
            # at the end of the test. Files in the temporary folder will be cleaned up
            # automatically.
            if event["status"] != "canceled" and not event["filepath"].startswith(temp_dir):
                downloaded_files.append(event["filepath"])

            return event
        except TimeoutException:
            print("User Agent showed file save dialog")
            return None

    yield trigger_download

    for file_path in downloaded_files:
        try:
            os.remove(file_path)
        except FileNotFoundError:
            print(f"File with path: {file_path} is not found")
            pass


@pytest.fixture
def is_download_allowed(trigger_download):
    """
    Returns True, if download is allowed, False if download is not allowed, or
    "timeout" if download is not finished (e.g. if User Agent showed file save
    dialog).
    """

    async def is_download_allowed(context):
        event = await trigger_download(context)
        if event is None:
            return "timeout"
        return event["status"] == "complete"

    return is_download_allowed


@pytest_asyncio.fixture
async def default_is_download_allowed(is_download_allowed, new_tab):
    return await is_download_allowed(new_tab)
