from __future__ import annotations

import threading
from collections.abc import Generator
from dataclasses import replace
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from typing import Any, cast

import pytest

from screenshot import ScreenshotCaptureOutcome
from screenshot.models import (
    CaptureOptions,
    MotionSnapshotRequest,
    ScreenshotArtifactsRequest,
    ScreenshotOptions,
)
from screenshot.playwright_runner import HIDE_OVERLAY_INIT_SCRIPT, capture_screenshots_async

pytestmark = pytest.mark.integration


class _SmokeHandler(BaseHTTPRequestHandler):
    def do_GET(self) -> None:  # noqa: N802 - signature defined by BaseHTTPRequestHandler
        if self.path == "/overlay-content":
            body = (
                "<html><head><title>Overlay Content</title></head>"
                "<body style='margin:0; background:#111; color:#fff;'>"
                "<div id='content-wrapper' class='overlay' "
                "style='display:block; visibility:visible; background:#064; padding:24px;'>"
                "<h1>Overlay Content</h1><p>legitimate primary content</p></div>"
                "</body></html>"
            )
        elif self.path == "/cookie-dialog":
            body = (
                "<html><head><title>Cookie Dialog</title></head>"
                "<body style='margin:0; background:#fafafa;'>"
                "<div id='cookie-banner' class='cookie-banner' "
                "style='display:block; visibility:visible; position:fixed; inset:0;"
                "background:rgba(0,0,0,0.8); color:#fff; padding:24px;'>"
                "Please accept cookies</div>"
                "<main id='main-content' style='padding:24px;'>Main content</main>"
                "</body></html>"
            )
        else:
            body = (
                "<html><head><title>Smoke</title></head>"
                "<body><h1>Smoke Test</h1><p id='content'>ok</p></body></html>"
            )
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.end_headers()
        self.wfile.write(body.encode("utf-8"))

    def log_message(self, format: str, *args: object) -> None:  # noqa: A003
        return  # keep pytest output clean


@pytest.fixture(scope="module")
def smoke_site() -> Generator[str, None, None]:
    try:
        server = HTTPServer(("127.0.0.1", 0), _SmokeHandler)
    except PermissionError:
        pytest.skip(
            "Cannot bind HTTP server inside this environment; smoke test needs network access"
        )
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    base = f"http://127.0.0.1:{server.server_address[1]}"
    try:
        yield base
    finally:
        server.shutdown()
        thread.join()


def _smoke_options(**capture_overrides: Any) -> ScreenshotOptions:
    capture = CaptureOptions(
        enabled=True,
        max_pages=1,
        depth=0,
        viewports=("desktop",),
        post_nav_wait_s=0.1,
        timeout_s=20.0,
        max_capture_attempts=1,
    )
    if capture_overrides:
        capture = replace(capture, **capture_overrides)
    return ScreenshotOptions(capture=capture)


async def _computed_style_with_overlay_init(*, url: str, selector: str) -> dict[str, str]:
    pytest.importorskip("playwright.async_api")

    try:
        from playwright.async_api import async_playwright
    except ImportError as exc:  # pragma: no cover - guarded by importorskip
        raise AssertionError("playwright.async_api should be importable here") from exc

    try:
        async with async_playwright() as playwright:
            browser = await playwright.chromium.launch()
            try:
                context = await browser.new_context()
                await context.add_init_script(HIDE_OVERLAY_INIT_SCRIPT)
                page = await context.new_page()
                await page.goto(url, wait_until="load")
                await page.wait_for_timeout(50)
                styles = cast(
                    dict[str, str],
                    await page.eval_on_selector(
                        selector,
                        """(element) => {
                        const computed = window.getComputedStyle(element);
                        return {
                            display: computed.display,
                            visibility: computed.visibility,
                        };
                    }""",
                    ),
                )
                await context.close()
                return styles
            finally:
                await browser.close()
    except Exception as exc:
        message = str(exc).lower()
        if "playwright" in message or "browser" in message or "executable" in message:
            pytest.skip(
                "Playwright browser unavailable; run `playwright install chromium` "
                "before rerunning this smoke test."
            )
        raise


@pytest.mark.asyncio
async def test_capture_local_site_smoke(tmp_path: Path, smoke_site: str) -> None:
    pytest.importorskip("playwright.async_api")

    result = await capture_screenshots_async(
        "smoke",
        f"{smoke_site}/",
        store_dir=tmp_path,
        partition_date=None,
        options=_smoke_options(),
    )

    if result.errors and any("playwright" in err.message.lower() for err in result.errors):
        pytest.skip(
            "Playwright capture failed; run `playwright install chromium` "
            "(and ensure the browsers are available) before rerunning the smoke test."
        )

    assert result.outcome is ScreenshotCaptureOutcome.SUCCESS
    assert result.metadata_path and result.metadata_path.exists()
    entries = result.entries
    assert entries, "Expected metadata entries for smoke job"


@pytest.mark.asyncio
async def test_capture_failure_records_errors(tmp_path: Path) -> None:
    pytest.importorskip("playwright.async_api")

    bad_url = "http://127.0.0.1:9/does-not-exist"
    result = await capture_screenshots_async(
        "smoke-failure",
        bad_url,
        store_dir=tmp_path,
        partition_date=None,
        options=_smoke_options(timeout_s=5.0),
    )

    assert result.outcome is not ScreenshotCaptureOutcome.SUCCESS
    assert result.errors or any(
        isinstance(entry, dict) and entry.get("error") for entry in result.entries
    )


@pytest.mark.asyncio
async def test_capture_local_site_motion_snapshot_smoke(tmp_path: Path, smoke_site: str) -> None:
    pytest.importorskip("playwright.async_api")
    pytest.importorskip("PIL.Image")

    motion_path = tmp_path / "motion.gif"
    result = await capture_screenshots_async(
        "smoke-motion",
        f"{smoke_site}/",
        store_dir=tmp_path,
        partition_date=None,
        options=_smoke_options(post_nav_wait_s=0.0, pre_capture_wait_s=0.0),
        artifacts=ScreenshotArtifactsRequest(
            motion_snapshot=MotionSnapshotRequest(path=motion_path, duration_s=0.6, fps=2)
        ),
    )

    if result.errors and any("playwright" in err.message.lower() for err in result.errors):
        pytest.skip(
            "Playwright capture failed; run `playwright install chromium` "
            "(and ensure the browsers are available) before rerunning the smoke test."
        )

    assert result.outcome is ScreenshotCaptureOutcome.SUCCESS
    assert motion_path.exists()
    assert result.artifacts.motion_snapshot is not None
    assert result.artifacts.motion_snapshot.written is True


@pytest.mark.asyncio
async def test_overlay_hiding_preserves_legitimate_overlay_content(smoke_site: str) -> None:
    styles = await _computed_style_with_overlay_init(
        url=f"{smoke_site}/overlay-content",
        selector="#content-wrapper",
    )

    assert styles["display"] != "none"
    assert styles["visibility"] != "hidden"


@pytest.mark.asyncio
async def test_overlay_hiding_still_hides_cookie_banner(smoke_site: str) -> None:
    styles = await _computed_style_with_overlay_init(
        url=f"{smoke_site}/cookie-dialog",
        selector="#cookie-banner",
    )

    assert styles["display"] == "none" or styles["visibility"] == "hidden"
