from __future__ import annotations

import importlib.util
import json
import os
import sys
from pathlib import Path
from types import ModuleType
from typing import Any, cast

import pytest

from screenshot import ScreenshotBatchResult, ScreenshotCaptureResult, ScreenshotOptions


def _load_module(name: str, path: Path) -> ModuleType:
    spec = importlib.util.spec_from_file_location(name, path)
    if spec is None or spec.loader is None:
        raise AssertionError(f"Unable to load module from {path}")
    module = importlib.util.module_from_spec(spec)
    sys.modules[name] = module
    spec.loader.exec_module(module)
    return module


@pytest.mark.asyncio
async def test_playwright_programmatic_capture_example_runs_with_stubbed_runner() -> None:
    module = _load_module(
        "example_playwright_programmatic_capture",
        Path(__file__).resolve().parents[1] / "examples" / "playwright_programmatic_capture.py",
    )
    module_any = cast(Any, module)

    async def fake_capture(*_args: object, **_kwargs: object) -> ScreenshotCaptureResult:
        return ScreenshotCaptureResult(
            requested=1,
            captured=1,
            failed=0,
            metadata_path=None,
            entries=[],
            errors=[],
            job_id="example-playwright",
        )

    module_any.capture_screenshots_async = fake_capture
    await module_any.main()


@pytest.mark.asyncio
async def test_selenium_programmatic_capture_example_runs_with_stubbed_runner() -> None:
    module = _load_module(
        "example_selenium_programmatic_capture",
        Path(__file__).resolve().parents[1] / "examples" / "selenium_programmatic_capture.py",
    )
    module_any = cast(Any, module)

    async def fake_capture(*_args: object, **_kwargs: object) -> ScreenshotCaptureResult:
        return ScreenshotCaptureResult(
            requested=1,
            captured=1,
            failed=0,
            metadata_path=None,
            entries=[],
            errors=[],
            job_id="example-selenium",
        )

    module_any.capture_screenshots_async_selenium = fake_capture
    await module_any.main()


@pytest.mark.asyncio
async def test_playwright_scrolling_depth_capture_example_runs_with_stubbed_runner() -> None:
    module = _load_module(
        "example_playwright_scrolling_depth_capture",
        Path(__file__).resolve().parents[1] / "examples" / "playwright_scrolling_depth_capture.py",
    )
    module_any = cast(Any, module)

    captured_kwargs: dict[str, object] = {}

    async def fake_capture(*_args: object, **_kwargs: object) -> ScreenshotCaptureResult:
        captured_kwargs.update(_kwargs)
        return ScreenshotCaptureResult(
            requested=3,
            captured=3,
            failed=0,
            metadata_path=None,
            entries=[],
            errors=[],
            job_id="example-scroll-depth",
        )

    module_any.capture_screenshots_async = fake_capture
    await module_any.main()

    options = cast(ScreenshotOptions, captured_kwargs["options"])
    assert options.capture.depth == 1
    assert options.capture.max_pages == 3
    assert options.capture.scroll is True
    assert options.capture.full_page is True
    assert options.capture.scroll_step_delay_ms == 150
    assert options.capture.max_scroll_steps == 20


@pytest.mark.asyncio
async def test_playwright_motion_snapshot_example_runs_with_stubbed_runner() -> None:
    module = _load_module(
        "example_playwright_motion_snapshot",
        Path(__file__).resolve().parents[1] / "examples" / "playwright_motion_snapshot.py",
    )
    module_any = cast(Any, module)

    captured_kwargs: dict[str, object] = {}

    async def fake_capture(*_args: object, **_kwargs: object) -> ScreenshotCaptureResult:
        captured_kwargs.update(_kwargs)
        return ScreenshotCaptureResult(
            requested=1,
            captured=1,
            failed=0,
            metadata_path=None,
            entries=[],
            errors=[],
            job_id="example-motion",
        )

    module_any.capture_screenshots_async = fake_capture
    await module_any.main()

    artifacts = cast(Any, captured_kwargs["artifacts"])
    assert artifacts.motion_snapshot is not None
    assert artifacts.motion_snapshot.path == Path(
        "playwright-motion-output/artifacts/landing-page.gif"
    )
    assert artifacts.motion_snapshot.duration_s == 2.0
    assert artifacts.motion_snapshot.fps == 4
    options = cast(ScreenshotOptions, captured_kwargs["options"])
    assert options.capture.scroll is False
    assert options.capture.pre_capture_wait_s == 0.0


@pytest.mark.asyncio
async def test_service_batch_capture_example_runs_with_stubbed_service() -> None:
    module = _load_module(
        "example_service_batch_capture",
        Path(__file__).resolve().parents[1] / "examples" / "service_batch_capture.py",
    )
    module_any = cast(Any, module)

    class StubService:
        async def capture_async(self, jobs: list[object]) -> ScreenshotBatchResult:
            assert len(jobs) == 2
            return ScreenshotBatchResult(
                results=[
                    ScreenshotCaptureResult(
                        requested=1,
                        captured=1,
                        failed=0,
                        metadata_path=None,
                        entries=[],
                        errors=[],
                        job_id="example-home",
                    ),
                    ScreenshotCaptureResult(
                        requested=1,
                        captured=1,
                        failed=0,
                        metadata_path=None,
                        entries=[],
                        errors=[],
                        job_id="python-home",
                    ),
                ]
            )

    module_any.build_local_screenshot_service = lambda **_kwargs: StubService()
    await module_any.main()


@pytest.mark.asyncio
async def test_storage_summary_integration_example_runs_with_stubbed_service() -> None:
    module = _load_module(
        "example_storage_summary_integration",
        Path(__file__).resolve().parents[1] / "examples" / "storage_summary_integration.py",
    )
    module_any = cast(Any, module)

    class FakeStorage:
        def write_json(self, path: Path, _data: dict[str, object]) -> Path:
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_text("{}", encoding="utf-8")
            return path

    class StubService:
        async def capture_async(self, jobs: list[object]) -> ScreenshotBatchResult:
            assert len(jobs) == 1
            return ScreenshotBatchResult(
                results=[
                    ScreenshotCaptureResult(
                        requested=1,
                        captured=1,
                        failed=0,
                        metadata_path=None,
                        entries=[],
                        errors=[],
                        job_id="example-storage",
                    )
                ]
            )

    module_any.LocalStorageBackend = FakeStorage
    module_any.build_local_screenshot_service = lambda **_kwargs: StubService()
    await module_any.main()


def test_cli_jsonl_batch_capture_example_writes_nested_jobs_file(tmp_path: Path) -> None:
    module = _load_module(
        "example_cli_jsonl_batch_capture",
        Path(__file__).resolve().parents[1] / "examples" / "cli_jsonl_batch_capture.py",
    )
    module_any = cast(Any, module)

    cwd = Path.cwd()
    try:
        os.chdir(tmp_path)
        module_any.main()
    finally:
        os.chdir(cwd)

    input_path = tmp_path / "cli-jsonl-example" / "jobs.jsonl"
    assert input_path.exists()
    records = [json.loads(line) for line in input_path.read_text(encoding="utf-8").splitlines()]
    assert len(records) == 2
    assert records[0]["options"]["schema_version"] == "screenshot_options/v2"
    assert records[0]["metadata"]["source"] == "cli-jsonl-example"
    assert records[0]["backend"] == "playwright"
    assert records[0]["artifacts"]["html_snapshot"]["path"].endswith("marketing-home.html")
    assert records[0]["artifacts"]["motion_snapshot"]["path"].endswith("marketing-home.gif")


@pytest.mark.asyncio
async def test_custom_viewport_and_html_snapshot_example_runs_with_stubbed_runner() -> None:
    module = _load_module(
        "example_custom_viewport_and_html_snapshot",
        Path(__file__).resolve().parents[1] / "examples" / "custom_viewport_and_html_snapshot.py",
    )
    module_any = cast(Any, module)

    captured_kwargs: dict[str, object] = {}

    async def fake_capture(*_args: object, **_kwargs: object) -> ScreenshotCaptureResult:
        captured_kwargs.update(_kwargs)
        return ScreenshotCaptureResult(
            requested=1,
            captured=1,
            failed=0,
            metadata_path=Path("custom-viewport-output/metadata/screenshots.json"),
            entries=[],
            errors=[],
            job_id="custom-viewport",
        )

    module_any.capture_screenshots_async = fake_capture
    await module_any.main()

    assert captured_kwargs["job_metadata"] == {"campaign": "landing-refresh", "source": "example"}
    artifacts = cast(Any, captured_kwargs["artifacts"])
    assert artifacts.html_snapshot is not None
    assert artifacts.html_snapshot.path == Path("custom-viewport-output/html/landing-page.html")
    options = cast(ScreenshotOptions, captured_kwargs["options"])
    viewport = options.capture.viewports[0]
    assert isinstance(viewport, dict)
    assert viewport["name"] == "landing"
