"""Integration tests for event exception handlers."""

from __future__ import annotations

import time
from typing import Generator, Type

import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from reflex.testing import AppHarness, AppHarnessProd

pytestmark = [pytest.mark.ignore_console_error]


def TestApp():
    """A test app for event exception handler integration."""
    import reflex as rx

    class TestAppConfig(rx.Config):
        """Config for the TestApp app."""

        pass

    class TestAppState(rx.State):
        """State for the TestApp app."""

        react_error: bool = False

        def divide_by_number(self, number: int):
            """Divide by number and print the result.

            Args:
                number: number to divide by

            """
            print(1 / number)

    app = rx.App(_state=rx.State)

    @app.add_page
    def index():
        return rx.vstack(
            rx.button(
                "induce_frontend_error",
                on_click=rx.call_script("induce_frontend_error()"),
                id="induce-frontend-error-btn",
            ),
            rx.button(
                "induce_backend_error",
                on_click=lambda: TestAppState.divide_by_number(0),  # pyright: ignore [reportCallIssue]
                id="induce-backend-error-btn",
            ),
            rx.button(
                "induce_react_error",
                on_click=TestAppState.set_react_error(True),  # pyright: ignore [reportAttributeAccessIssue]
                id="induce-react-error-btn",
            ),
            rx.box(
                rx.cond(
                    TestAppState.react_error,
                    rx.Var.create({"invalid": "cannot have object as child"}),
                    "",
                ),
            ),
        )


@pytest.fixture(scope="module")
def test_app(
    app_harness_env: Type[AppHarness], tmp_path_factory
) -> Generator[AppHarness, None, None]:
    """Start TestApp app at tmp_path via AppHarness.

    Args:
        app_harness_env: either AppHarness (dev) or AppHarnessProd (prod)
        tmp_path_factory: pytest tmp_path_factory fixture

    Yields:
        running AppHarness instance

    """
    with app_harness_env.create(
        root=tmp_path_factory.mktemp("test_app"),
        app_name=f"testapp_{app_harness_env.__name__.lower()}",
        app_source=TestApp,
    ) as harness:
        yield harness


@pytest.fixture
def driver(test_app: AppHarness) -> Generator[WebDriver, None, None]:
    """Get an instance of the browser open to the test_app app.

    Args:
        test_app: harness for TestApp app

    Yields:
        WebDriver instance.

    """
    assert test_app.app_instance is not None, "app is not running"
    driver = test_app.frontend()
    try:
        yield driver
    finally:
        driver.quit()


def test_frontend_exception_handler_during_runtime(
    driver: WebDriver,
    capsys,
):
    """Test calling frontend exception handler during runtime.

    We send an event containing a call to a non-existent function in the frontend.
    This should trigger the default frontend exception handler.

    Args:
        driver: WebDriver instance.
        capsys: pytest fixture for capturing stdout and stderr.

    """
    reset_button = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.ID, "induce-frontend-error-btn"))
    )

    reset_button.click()

    # Wait for the error to be logged
    time.sleep(2)

    captured_default_handler_output = capsys.readouterr()
    assert (
        "induce_frontend_error" in captured_default_handler_output.out
        and "ReferenceError" in captured_default_handler_output.out
    )


def test_backend_exception_handler_during_runtime(
    driver: WebDriver,
    capsys,
):
    """Test calling backend exception handler during runtime.

    We invoke TestAppState.divide_by_zero to induce backend error.
    This should trigger the default backend exception handler.

    Args:
        driver: WebDriver instance.
        capsys: pytest fixture for capturing stdout and stderr.

    """
    reset_button = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.ID, "induce-backend-error-btn"))
    )

    reset_button.click()

    # Wait for the error to be logged
    time.sleep(2)

    captured_default_handler_output = capsys.readouterr()
    assert (
        "divide_by_number" in captured_default_handler_output.out
        and "ZeroDivisionError" in captured_default_handler_output.out
    )


def test_frontend_exception_handler_with_react(
    test_app: AppHarness,
    driver: WebDriver,
    capsys,
):
    """Test calling frontend exception handler during runtime.

    Render an object as a react child, which is invalid.

    Args:
        test_app: harness for TestApp app
        driver: WebDriver instance.
        capsys: pytest fixture for capturing stdout and stderr.

    """
    reset_button = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.ID, "induce-react-error-btn"))
    )

    reset_button.click()

    # Wait for the error to be logged
    time.sleep(2)

    captured_default_handler_output = capsys.readouterr()
    if isinstance(test_app, AppHarnessProd):
        assert "Error: Minified React error #31" in captured_default_handler_output.out
    else:
        assert (
            "Error: Objects are not valid as a React child (found: object with keys \n{invalid})"
            in captured_default_handler_output.out
        )