from __future__ import annotations

import json
from collections.abc import Iterable
from pathlib import Path

import pytest

from screenshot import (
    ScreenshotBatchResult,
    ScreenshotCaptureResult,
    ScreenshotOptions,
    cli as screenshot_cli,
)
from screenshot.cli_utils import ResolvedJobSpec


class _FakeService:
    def __init__(self, batch: ScreenshotBatchResult) -> None:
        self._batch = batch

    def capture(self, jobs: Iterable[object]) -> ScreenshotBatchResult:
        del jobs
        return self._batch


def test_cmd_local_writes_summary(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    summary_file = tmp_path / "out" / "summary.json"

    success = ScreenshotCaptureResult(
        requested=1,
        captured=1,
        failed=0,
        metadata_path=None,
        entries=[],
        errors=[],
        job_id="job-1",
    )
    batch = ScreenshotBatchResult(results=[success])

    monkeypatch.setattr(
        screenshot_cli,
        "collect_job_specs",
        lambda args: [
            ResolvedJobSpec(
                job_id="job-1",
                url="https://example.com",
                partition_date=None,
                metadata={},
                options=ScreenshotOptions(),
                backend="playwright",
            )
        ],
    )
    monkeypatch.setattr(
        screenshot_cli,
        "_build_local_screenshot_service",
        lambda concurrency=1, backend="playwright", storage=None: _FakeService(batch),
    )

    parser = screenshot_cli.build_parser()
    args = parser.parse_args(
        [
            "local",
            "--urls",
            "https://example.com",
            "--output-dir",
            str(tmp_path / "out"),
            "--summary-file",
            str(summary_file),
        ]
    )

    exit_code = screenshot_cli._cmd_local(args)

    assert exit_code == 0
    assert summary_file.exists()
    payload = json.loads(summary_file.read_text(encoding="utf-8"))
    assert payload["success_count"] == 1
    assert payload["failure_count"] == 0
    assert payload["partial_count"] == 0
    assert payload["skipped_count"] == 0
    assert payload["outcome_counts"]["success"] == 1
    assert payload["results"][0]["outcome"] == "success"


def test_cmd_local_handles_output_dir_creation_failure(
    monkeypatch: pytest.MonkeyPatch,
    caplog: pytest.LogCaptureFixture,
    tmp_path: Path,
) -> None:
    output_dir = tmp_path / "out"

    monkeypatch.setattr(
        screenshot_cli,
        "collect_job_specs",
        lambda args: [
            ResolvedJobSpec(
                job_id="job-1",
                url="https://example.com",
                partition_date=None,
                metadata={},
                options=ScreenshotOptions(),
                backend="playwright",
            )
        ],
    )
    monkeypatch.setattr(
        screenshot_cli,
        "_build_local_screenshot_service",
        lambda concurrency=1, backend="playwright", storage=None: pytest.fail(
            "service should not be built when output dir creation fails"
        ),
    )

    original_mkdir = Path.mkdir

    def fail_mkdir(
        self: Path,
        mode: int = 0o777,
        parents: bool = False,
        exist_ok: bool = False,
    ) -> None:
        if self == output_dir:
            raise OSError("read-only filesystem")
        original_mkdir(self, mode=mode, parents=parents, exist_ok=exist_ok)

    monkeypatch.setattr(Path, "mkdir", fail_mkdir)
    caplog.set_level("ERROR")

    parser = screenshot_cli.build_parser()
    args = parser.parse_args(
        ["local", "--urls", "https://example.com", "--output-dir", str(output_dir)]
    )

    exit_code = screenshot_cli._cmd_local(args)

    assert exit_code == 1
    assert "Failed to create output directory" in caplog.text


def test_cmd_local_handles_summary_write_failure(
    monkeypatch: pytest.MonkeyPatch,
    caplog: pytest.LogCaptureFixture,
    tmp_path: Path,
) -> None:
    summary_file = tmp_path / "out" / "summary.json"

    success = ScreenshotCaptureResult(
        requested=1,
        captured=1,
        failed=0,
        metadata_path=None,
        entries=[],
        errors=[],
        job_id="job-1",
    )
    batch = ScreenshotBatchResult(results=[success])

    monkeypatch.setattr(
        screenshot_cli,
        "collect_job_specs",
        lambda args: [
            ResolvedJobSpec(
                job_id="job-1",
                url="https://example.com",
                partition_date=None,
                metadata={},
                options=ScreenshotOptions(),
                backend="playwright",
            )
        ],
    )
    monkeypatch.setattr(
        screenshot_cli,
        "_build_local_screenshot_service",
        lambda concurrency=1, backend="playwright", storage=None: _FakeService(batch),
    )

    original_write_text = Path.write_text

    def fail_write_text(
        self: Path,
        data: str,
        encoding: str | None = None,
        errors: str | None = None,
        newline: str | None = None,
    ) -> int:
        if self == summary_file:
            raise OSError("disk full")
        return original_write_text(
            self,
            data,
            encoding=encoding,
            errors=errors,
            newline=newline,
        )

    monkeypatch.setattr(Path, "write_text", fail_write_text)
    caplog.set_level("ERROR")

    parser = screenshot_cli.build_parser()
    args = parser.parse_args(
        [
            "local",
            "--urls",
            "https://example.com",
            "--output-dir",
            str(tmp_path / "out"),
            "--summary-file",
            str(summary_file),
        ]
    )

    exit_code = screenshot_cli._cmd_local(args)

    assert exit_code == 1
    assert "Failed to write screenshot summary" in caplog.text


def test_cmd_local_handles_service_capture_exception(
    monkeypatch: pytest.MonkeyPatch,
    caplog: pytest.LogCaptureFixture,
    tmp_path: Path,
) -> None:
    class _ExplodingService:
        def capture(self, jobs: Iterable[object]) -> ScreenshotBatchResult:
            del jobs
            raise RuntimeError("backend exploded")

    monkeypatch.setattr(
        screenshot_cli,
        "collect_job_specs",
        lambda args: [
            ResolvedJobSpec(
                job_id="job-1",
                url="https://example.com",
                partition_date=None,
                metadata={},
                options=ScreenshotOptions(),
                backend="playwright",
            )
        ],
    )
    monkeypatch.setattr(
        screenshot_cli,
        "_build_local_screenshot_service",
        lambda concurrency=1, backend="playwright", storage=None: _ExplodingService(),
    )
    caplog.set_level("ERROR")

    parser = screenshot_cli.build_parser()
    args = parser.parse_args(
        ["local", "--urls", "https://example.com", "--output-dir", str(tmp_path / "out")]
    )

    exit_code = screenshot_cli._cmd_local(args)

    assert exit_code == 1
    assert "Screenshot capture failed" in caplog.text


@pytest.mark.parametrize("site_concurrency", [0, -2])
def test_cmd_local_rejects_non_positive_site_concurrency(
    monkeypatch: pytest.MonkeyPatch,
    caplog: pytest.LogCaptureFixture,
    tmp_path: Path,
    site_concurrency: int,
) -> None:
    monkeypatch.setattr(
        screenshot_cli,
        "collect_job_specs",
        lambda args: [
            ResolvedJobSpec(
                job_id="job-1",
                url="https://example.com",
                partition_date=None,
                metadata={},
                options=ScreenshotOptions(),
                backend="playwright",
            )
        ],
    )
    caplog.set_level("ERROR")

    parser = screenshot_cli.build_parser()
    args = parser.parse_args(
        [
            "local",
            "--urls",
            "https://example.com",
            "--output-dir",
            str(tmp_path / "out"),
            "--site-concurrency",
            str(site_concurrency),
        ]
    )

    exit_code = screenshot_cli._cmd_local(args)

    assert exit_code == 1
    assert "concurrency must be positive" in caplog.text
