"""Tests for dataclass validation and utility functions."""

import pytest
from screenshot import ScreenshotOptions

from screenshot_batch import (
    ScreenshotBatchResult,
    ScreenshotJobSpec,
    is_batch_successful,
)


class TestScreenshotJobSpecValidation:
    """Tests for ScreenshotJobSpec validation."""

    def test_validates_job_id_not_empty(self):
        """Raises ValueError if job_id is empty."""
        with pytest.raises(ValueError, match="job_id must be a non-empty string"):
            ScreenshotJobSpec(
                job_id="",
                url="https://example.com",
                options=ScreenshotOptions(),
            )

    def test_validates_job_id_not_whitespace(self):
        """Raises ValueError if job_id is only whitespace."""
        with pytest.raises(ValueError, match="job_id must be a non-empty string"):
            ScreenshotJobSpec(
                job_id="   ",
                url="https://example.com",
                options=ScreenshotOptions(),
            )

    def test_validates_url_not_empty(self):
        """Raises ValueError if url is empty."""
        with pytest.raises(ValueError, match="url must be a non-empty string"):
            ScreenshotJobSpec(
                job_id="job-1",
                url="",
                options=ScreenshotOptions(),
            )

    def test_validates_url_not_whitespace(self):
        """Raises ValueError if url is only whitespace."""
        with pytest.raises(ValueError, match="url must be a non-empty string"):
            ScreenshotJobSpec(
                job_id="job-1",
                url="   ",
                options=ScreenshotOptions(),
            )

    def test_accepts_valid_spec(self):
        """Creates spec successfully with valid inputs."""
        spec = ScreenshotJobSpec(
            job_id="job-1",
            url="https://example.com",
            options=ScreenshotOptions(),
        )
        assert spec.job_id == "job-1"
        assert spec.url == "https://example.com"

    def test_spec_is_immutable(self):
        """ScreenshotJobSpec is frozen and immutable."""
        spec = ScreenshotJobSpec(
            job_id="job-1",
            url="https://example.com",
            options=ScreenshotOptions(),
        )

        with pytest.raises((AttributeError, TypeError)):  # FrozenInstanceError manifests as one of these
            spec.job_id = "job-2"

    def test_from_url_factory_method(self):
        """from_url factory method creates valid spec."""
        spec = ScreenshotJobSpec.from_url("https://example.com")

        assert spec.url == "https://example.com"
        assert spec.job_id  # Should be auto-generated
        assert spec.options.capture.enabled  # Should be enabled

    def test_from_url_raises_on_empty_url(self):
        """from_url raises ValueError for empty URL."""
        with pytest.raises(ValueError, match="url must be a non-empty string"):
            ScreenshotJobSpec.from_url("")

    def test_from_url_with_custom_job_id(self):
        """from_url accepts custom job_id."""
        spec = ScreenshotJobSpec.from_url(
            "https://example.com",
            job_id="custom-id",
        )
        assert spec.job_id == "custom-id"

    def test_from_url_with_metadata(self):
        """from_url accepts metadata dict."""
        spec = ScreenshotJobSpec.from_url(
            "https://example.com",
            metadata={"source": "test"},
        )
        assert spec.metadata["source"] == "test"

    def test_to_payload_includes_all_fields(self):
        """to_payload includes all specified fields."""
        spec = ScreenshotJobSpec(
            job_id="job-1",
            url="https://example.com",
            options=ScreenshotOptions(),
            partition_date="2025-01-18",
            metadata={"priority": "high"},
        )

        payload = spec.to_payload()

        assert payload["job_id"] == "job-1"
        assert payload["url"] == "https://example.com"
        assert "options" in payload
        assert payload["partition_date"] == "2025-01-18"
        assert payload["metadata"]["priority"] == "high"

    def test_to_payload_merges_default_metadata(self):
        """to_payload merges default_metadata with spec metadata."""
        spec = ScreenshotJobSpec(
            job_id="job-1",
            url="https://example.com",
            options=ScreenshotOptions(),
            metadata={"priority": "high"},
        )

        payload = spec.to_payload(default_metadata={"source": "batch"})

        assert payload["metadata"]["source"] == "batch"
        assert payload["metadata"]["priority"] == "high"

    def test_to_payload_spec_metadata_overrides_default(self):
        """Spec metadata overrides default_metadata for same keys."""
        spec = ScreenshotJobSpec(
            job_id="job-1",
            url="https://example.com",
            options=ScreenshotOptions(),
            metadata={"source": "custom"},
        )

        payload = spec.to_payload(default_metadata={"source": "batch"})

        assert payload["metadata"]["source"] == "custom"


class TestScreenshotBatchResultValidation:
    """Tests for ScreenshotBatchResult validation."""

    def test_validates_job_total_non_negative(self):
        """Raises ValueError if job_total is negative."""
        with pytest.raises(ValueError, match="job_total must be >= 0"):
            ScreenshotBatchResult(
                batch_id="batch-1",
                job_id="job-1",
                status="completed",
                job_total=-1,
                job_completed=0,
                job_failed=0,
            )

    def test_validates_job_completed_non_negative(self):
        """Raises ValueError if job_completed is negative."""
        with pytest.raises(ValueError, match="job_completed must be >= 0"):
            ScreenshotBatchResult(
                batch_id="batch-1",
                job_id="job-1",
                status="completed",
                job_total=10,
                job_completed=-1,
                job_failed=0,
            )

    def test_validates_job_failed_non_negative(self):
        """Raises ValueError if job_failed is negative."""
        with pytest.raises(ValueError, match="job_failed must be >= 0"):
            ScreenshotBatchResult(
                batch_id="batch-1",
                job_id="job-1",
                status="completed",
                job_total=10,
                job_completed=9,
                job_failed=-1,
            )

    def test_accepts_valid_result(self):
        """Creates result successfully with valid inputs."""
        result = ScreenshotBatchResult(
            batch_id="batch-1",
            job_id="job-1",
            status="completed",
            job_total=10,
            job_completed=9,
            job_failed=1,
        )
        assert result.batch_id == "batch-1"
        assert result.job_total == 10

    def test_result_is_immutable(self):
        """ScreenshotBatchResult is frozen and immutable."""
        result = ScreenshotBatchResult(
            batch_id="batch-1",
            job_id="job-1",
            status="completed",
            job_total=10,
            job_completed=10,
            job_failed=0,
        )

        with pytest.raises((AttributeError, TypeError)):  # FrozenInstanceError manifests as one of these
            result.status = "failed"


class TestIsBatchSuccessful:
    """Tests for is_batch_successful utility function."""

    def test_returns_true_for_completed_with_no_failures(self):
        """Returns True when status is completed and no jobs failed."""
        result = ScreenshotBatchResult(
            batch_id="batch-1",
            job_id="job-1",
            status="completed",
            job_total=10,
            job_completed=10,
            job_failed=0,
        )
        assert is_batch_successful(result) is True

    def test_returns_false_for_completed_with_failures(self):
        """Returns False when status is completed but some jobs failed."""
        result = ScreenshotBatchResult(
            batch_id="batch-1",
            job_id="job-1",
            status="completed",
            job_total=10,
            job_completed=9,
            job_failed=1,
        )
        assert is_batch_successful(result) is False

    def test_returns_false_for_failed_status(self):
        """Returns False when status is not completed."""
        result = ScreenshotBatchResult(
            batch_id="batch-1",
            job_id="job-1",
            status="failed",
            job_total=10,
            job_completed=0,
            job_failed=10,
        )
        assert is_batch_successful(result) is False

    def test_returns_false_for_timeout_status(self):
        """Returns False for timeout status."""
        result = ScreenshotBatchResult(
            batch_id="batch-1",
            job_id="job-1",
            status="timeout",
            job_total=10,
            job_completed=5,
            job_failed=0,
        )
        assert is_batch_successful(result) is False

    def test_returns_false_for_unknown_status(self):
        """Returns False for unknown status."""
        result = ScreenshotBatchResult(
            batch_id="batch-1",
            job_id="job-1",
            status="unknown",
            job_total=10,
            job_completed=10,
            job_failed=0,
        )
        assert is_batch_successful(result) is False
