from __future__ import annotations

from pathlib import Path
from typing import Any

import pytest

from screenshot import (
    HtmlSnapshotRequest,
    MotionSnapshotRequest,
    ScreenshotArtifactsRequest,
    ScreenshotCaptureOutcome,
    ScreenshotCaptureResult,
    ScreenshotJob,
    ScreenshotOptions,
)
from screenshot._models_options import CaptureOptions
from screenshot.backends import (
    PlaywrightScreenshotBackend,
    SeleniumScreenshotBackend,
    build_local_screenshot_service,
)


class _CancelToken:
    def raise_if_cancelled(self) -> None:
        return None


class _Storage:
    def ensure_parent(self, path: Path) -> None:
        del path

    def write_json(self, path: Path, data: dict[str, Any]) -> Path:
        del data
        return path

    def write_text(self, path: Path, text: str) -> Path:
        del text
        return path


@pytest.mark.asyncio
async def test_playwright_backend_passes_job_fields_and_backfills_job_id(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    captured_kwargs: dict[str, object] = {}

    async def fake_capture_async(*args: object, **kwargs: object) -> ScreenshotCaptureResult:
        del args
        captured_kwargs.update(kwargs)
        return ScreenshotCaptureResult(
            requested=1,
            captured=1,
            failed=0,
            metadata_path=None,
            entries=[],
            errors=[],
            job_id=None,
        )

    monkeypatch.setattr("screenshot.backends.playwright_capture_async", fake_capture_async)

    storage = _Storage()
    cancel_token = _CancelToken()
    snapshot_path = tmp_path / "page.html"
    job = ScreenshotJob(
        job_id="playwright-job",
        url="https://example.com/playwright",
        output_root=tmp_path / "artifacts",
        partition_date="2026-03-14",
        artifacts=ScreenshotArtifactsRequest(html_snapshot=HtmlSnapshotRequest(path=snapshot_path)),
        cancel_token=cancel_token,
        options=ScreenshotOptions(capture=CaptureOptions(enabled=True)),
        metadata={"campaign": "spring-launch"},
    )

    result = await PlaywrightScreenshotBackend(storage=storage).run_job_async(job)

    assert captured_kwargs["store_dir"] == job.output_root
    assert captured_kwargs["partition_date"] == "2026-03-14"
    assert captured_kwargs["artifacts"] == job.artifacts
    assert captured_kwargs["cancel_token"] is cancel_token
    assert captured_kwargs["options"] == job.options
    assert captured_kwargs["job_metadata"] == {"campaign": "spring-launch"}
    assert captured_kwargs["storage"] is storage
    assert result.job_id == "playwright-job"


@pytest.mark.asyncio
async def test_selenium_backend_preserves_runner_job_id_and_passes_job_fields(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    captured_kwargs: dict[str, object] = {}

    async def fake_capture_async(*args: object, **kwargs: object) -> ScreenshotCaptureResult:
        del args
        captured_kwargs.update(kwargs)
        return ScreenshotCaptureResult(
            requested=1,
            captured=1,
            failed=0,
            metadata_path=None,
            entries=[],
            errors=[],
            job_id="runner-job",
        )

    monkeypatch.setattr("screenshot.backends.selenium_capture_async", fake_capture_async)

    storage = _Storage()
    cancel_token = _CancelToken()
    job = ScreenshotJob(
        job_id="selenium-job",
        url="https://example.com/selenium",
        output_root=tmp_path / "artifacts",
        partition_date=None,
        cancel_token=cancel_token,
        options=ScreenshotOptions(capture=CaptureOptions(enabled=True)),
        metadata={"campaign": "spring-launch"},
    )

    result = await SeleniumScreenshotBackend(storage=storage).run_job_async(job)

    assert captured_kwargs["store_dir"] == job.output_root
    assert captured_kwargs["partition_date"] is None
    assert captured_kwargs["artifacts"] == job.artifacts
    assert captured_kwargs["cancel_token"] is cancel_token
    assert captured_kwargs["options"] == job.options
    assert captured_kwargs["job_metadata"] == {"campaign": "spring-launch"}
    assert captured_kwargs["storage"] is storage
    assert result.job_id == "runner-job"


def test_build_local_screenshot_service_normalizes_backend_name() -> None:
    service = build_local_screenshot_service(concurrency=2, backend=" Selenium ")

    assert isinstance(service._backend, SeleniumScreenshotBackend)
    assert service._default_concurrency == 2


@pytest.mark.asyncio
async def test_selenium_backend_rejects_motion_snapshots(tmp_path: Path) -> None:
    job = ScreenshotJob(
        job_id="selenium-motion",
        url="https://example.com/selenium",
        output_root=tmp_path / "artifacts",
        options=ScreenshotOptions(capture=CaptureOptions(enabled=True)),
        artifacts=ScreenshotArtifactsRequest(
            motion_snapshot=MotionSnapshotRequest(path=tmp_path / "clip.gif", required=True)
        ),
    )

    result = await SeleniumScreenshotBackend(storage=_Storage()).run_job_async(job)

    assert result.outcome is ScreenshotCaptureOutcome.FAILED
    assert result.requested == 0
    assert result.errors[0].message == "Selenium backend does not support motion snapshots"


def test_build_local_screenshot_service_rejects_unknown_backend() -> None:
    with pytest.raises(ValueError, match="Unsupported backend 'webkit'"):
        build_local_screenshot_service(backend="webkit")


@pytest.mark.parametrize("concurrency", [0, -1])
def test_build_local_screenshot_service_rejects_non_positive_concurrency(
    concurrency: int,
) -> None:
    with pytest.raises(ValueError, match="concurrency must be positive"):
        build_local_screenshot_service(concurrency=concurrency)
