"""Unit tests for CdpAdapter."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

import pytest

from nomo_e2e.adapters.cdp import CdpAdapter
from nomo_e2e.exceptions import WebViewElementNotFoundError


@dataclass
class FakePage:
    url_value: str = "http://localhost:3000"
    title_value: str = "Test Page"
    _elements: dict[str, bool] = field(default_factory=dict)
    _clicked: list[str] = field(default_factory=list)
    _filled: dict[str, str] = field(default_factory=dict)
    _visibility: dict[str, bool] = field(default_factory=lambda: {"*": True})

    def wait_for_selector(
        self, selector: str, *, timeout: float | None = None, state: str = "visible"
    ) -> None:
        if selector not in self._elements:
            if "*" in self._elements:
                return
            raise Exception(f"Timeout: {selector} not found")
        if state == "attached":
            return
        if selector in self._visibility and not self._visibility[selector]:
            raise Exception(f"Element {selector} is hidden")

    def click(self, selector: str, *, timeout: int | None = None) -> None:
        if selector not in self._elements:
            raise Exception(f"Element not found: {selector}")
        self._clicked.append(selector)

    def fill(self, selector: str, text: str, *, timeout: int | None = None) -> None:
        if selector not in self._elements:
            raise Exception(f"Element not found: {selector}")
        self._filled[selector] = text

    def locator(self, selector: str) -> FakeLocator:
        return FakeLocator(selector, self)

    def screenshot(self, *, timeout: int | None = None) -> bytes:
        return b"fake_screenshot_data"

    @property
    def url(self) -> str:
        return self.url_value

    def title(self) -> str:
        return self.title_value

    def evaluate(self, script: str, *args: Any) -> Any:
        if "readyState" in script:
            return "complete"
        if "title" in script:
            return self.title_value
        return None


@dataclass
class FakeLocator:
    selector: str
    page: FakePage

    def text_content(self, *, timeout: int | None = None) -> str | None:
        if self.selector not in self.page._elements:
            raise Exception(f"Element not found: {self.selector}")
        return f"text for {self.selector}"

    def get_attribute(self, attr: str, *, timeout: int | None = None) -> str | None:
        if self.selector not in self.page._elements:
            raise Exception(f"Element not found: {self.selector}")
        return f"value of {attr}"

    def screenshot(self, *, timeout: int | None = None) -> bytes:
        if self.selector not in self.page._elements:
            raise Exception(f"Element not found: {self.selector}")
        return b"element_screenshot"


@dataclass
class FakeContext:
    pages_list: list[FakePage] = field(default_factory=list)

    @property
    def pages(self) -> list[FakePage]:
        return self.pages_list


@dataclass
class FakeBrowser:
    contexts_list: list[FakeContext] = field(default_factory=list)

    @property
    def contexts(self) -> list[FakeContext]:
        return self.contexts_list

    def close(self) -> None:
        pass


class FakePlaywright:
    chromium: FakeChromium

    def __init__(self, browser: FakeBrowser) -> None:
        self.chromium = FakeChromium(browser)
        self.stopped = False

    def stop(self) -> None:
        self.stopped = True


class FakeChromium:
    def __init__(self, browser: FakeBrowser) -> None:
        self._browser = browser

    def connect_over_cdp(self, endpoint: str) -> FakeBrowser:
        return self._browser


def make_fake_playwright_and_browser(
    page: FakePage | None = None,
) -> tuple[FakePlaywright, FakeBrowser]:
    if page is None:
        page = FakePage()
    browser = FakeBrowser()
    context = FakeContext(pages_list=[page])
    browser.contexts_list = [context]
    playwright = FakePlaywright(browser)
    return playwright, browser


class TestCdpAdapterConnect:
    def test_connect_creates_session(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        assert adapter.is_connected

    def test_connect_with_existing_browser_uses_existing(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        assert adapter._browser is browser
        assert adapter._page is page

    def test_double_connect_is_noop(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        adapter.connect()
        assert adapter.is_connected


class TestCdpAdapterDisconnect:
    def test_disconnect_closes_browser(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        adapter.disconnect()
        assert not adapter.is_connected
        assert playwright.stopped


class TestCdpAdapterWaitForSelector:
    def test_wait_for_selector_returns_true_when_found(self) -> None:
        page = FakePage(_elements={"#btn": True})
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        assert adapter.wait_for_selector("#btn", timeout_ms=1000) is True

    def test_wait_for_selector_returns_false_when_not_found(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        assert adapter.wait_for_selector("#missing", timeout_ms=100) is False


class TestCdpAdapterClick:
    def test_click_element(self) -> None:
        page = FakePage(_elements={"#btn": True})
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        adapter.click("#btn")
        assert "#btn" in page._clicked

    def test_click_raises_when_not_found(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        with pytest.raises(WebViewElementNotFoundError):
            adapter.click("#missing")


class TestCdpAdapterTypeText:
    def test_type_text_element(self) -> None:
        page = FakePage(_elements={"#input": True})
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        adapter.type_text("#input", "hello world")
        assert page._filled.get("#input") == "hello world"

    def test_type_text_raises_when_not_found(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        with pytest.raises(WebViewElementNotFoundError):
            adapter.type_text("#missing", "text")


class TestCdpAdapterGetText:
    def test_get_text_returns_content(self) -> None:
        page = FakePage(_elements={"#label": True})
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        text = adapter.get_text("#label")
        assert text == "text for #label"

    def test_get_text_raises_when_not_found(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        with pytest.raises(WebViewElementNotFoundError):
            adapter.get_text("#missing")


class TestCdpAdapterGetAttribute:
    def test_get_attribute_returns_value(self) -> None:
        page = FakePage(_elements={"#input": True})
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        attr = adapter.get_attribute("#input", "value")
        assert attr == "value of value"

    def test_get_attribute_raises_when_not_found(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        with pytest.raises(WebViewElementNotFoundError):
            adapter.get_attribute("#missing", "href")


class TestCdpAdapterExecuteScript:
    def test_execute_script_returns_result(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        result = adapter.execute_script("return document.readyState")
        assert result == "complete"


class TestCdpAdapterScreenshot:
    def test_screenshot_full_page(self) -> None:
        page = FakePage()
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        result = adapter.screenshot("/tmp/test.png")
        assert result == b"fake_screenshot_data"

    def test_screenshot_element(self) -> None:
        page = FakePage(_elements={"#widget": True})
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        result = adapter.screenshot("/tmp/element.png", selector="#widget")
        assert result == b"element_screenshot"


class TestCdpAdapterProperties:
    def test_url_returns_page_url(self) -> None:
        page = FakePage(url_value="http://example.com/page")
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        assert adapter.url == "http://example.com/page"

    def test_title_returns_page_title(self) -> None:
        page = FakePage(title_value="Example Page")
        playwright, browser = make_fake_playwright_and_browser(page)
        adapter = CdpAdapter(
            endpoint="http://localhost:9222",
            playwright=playwright,
            browser=browser,
        )
        adapter.connect()
        assert adapter.title == "Example Page"
