"""Tests for utility functions."""

import dataclasses
from pathlib import Path
from unittest.mock import Mock

import pytest

from screenshot_batch.utils import (
    derive_job_id,
    ensure_capture_enabled,
    normalize_options_payload,
    serialize_options,
)


class TestDeriveJobId:
    """Tests for derive_job_id function."""

    def test_derives_from_domain(self):
        """Derives job ID from URL domain."""
        job_id = derive_job_id("https://example.com/page")
        assert job_id.startswith("example-com-")
        assert len(job_id) > len("example-com-")

    def test_sanitizes_domain(self):
        """Sanitizes domain by replacing dots and colons."""
        job_id = derive_job_id("https://sub.example.com:8080/page")
        assert "sub-example-com-8080" in job_id

    def test_same_url_gives_same_id(self):
        """Same URL produces consistent job ID."""
        url = "https://example.com/page"
        id1 = derive_job_id(url)
        id2 = derive_job_id(url)
        assert id1 == id2

    def test_different_urls_give_different_ids(self):
        """Different URLs produce different job IDs."""
        id1 = derive_job_id("https://example.com/page1")
        id2 = derive_job_id("https://example.com/page2")
        assert id1 != id2

    def test_uses_fallback_for_invalid_url(self):
        """Uses fallback prefix for invalid URLs."""
        job_id = derive_job_id("not a url", fallback="custom")
        assert job_id.startswith("custom-")

    def test_custom_fallback(self):
        """Respects custom fallback parameter."""
        job_id = derive_job_id("", fallback="myjob")
        assert job_id.startswith("myjob-")


class TestEnsureCaptureEnabled:
    """Tests for ensure_capture_enabled function."""

    def test_returns_same_when_already_enabled(self):
        """Returns original options when capture already enabled."""
        # Create mock options with capture enabled
        options = Mock()
        options.capture = Mock()
        options.capture.enabled = True

        result = ensure_capture_enabled(options)

        assert result is options

    def test_enables_capture_when_disabled(self):
        """Creates new options with capture enabled when disabled."""
        # This test requires actual ScreenshotOptions from screenshot package
        # For now, we'll test the logic path
        options = Mock()
        options.capture = Mock()
        options.capture.enabled = False

        # The function uses dataclasses.replace which requires actual dataclass
        # We'll skip this test or mock it properly when screenshot package is available
        pytest.skip("Requires screenshot package dataclasses")


class TestNormalizeOptionsPayload:
    """Tests for normalize_options_payload function."""

    def test_returns_properly_structured_payload_unchanged(self):
        """Returns payload unchanged if already properly structured."""
        payload = {"capture": {"enabled": True}, "browser": {"headless": True}}
        result = normalize_options_payload(payload)
        assert result == payload

    def test_wraps_flat_payload_with_capture(self):
        """Wraps flat payload with capture section."""
        payload = {"timeout": 30}
        result = normalize_options_payload(payload)

        assert "capture" in result
        assert result["capture"]["enabled"] is True
        assert result["timeout"] == 30

    def test_preserves_runner_section(self):
        """Preserves runner section if present."""
        payload = {"runner": {"max_retries": 3}}
        result = normalize_options_payload(payload)
        assert result == payload

    def test_preserves_browser_section(self):
        """Preserves browser section if present."""
        payload = {"browser": {"headless": False}}
        result = normalize_options_payload(payload)
        assert result == payload


class TestSerializeOptions:
    """Tests for serialize_options function."""

    def test_serializes_simple_dataclass(self):
        """Serializes simple dataclass to dict."""

        @dataclasses.dataclass
        class SimpleOptions:
            enabled: bool = True
            count: int = 5

        options = SimpleOptions()
        result = serialize_options(options)

        assert isinstance(result, dict)
        assert result["enabled"] is True
        assert result["count"] == 5

    def test_serializes_nested_dataclasses(self):
        """Serializes nested dataclasses."""

        @dataclasses.dataclass
        class Inner:
            value: int = 10

        @dataclasses.dataclass
        class Outer:
            inner: Inner = dataclasses.field(default_factory=Inner)

        options = Outer()
        result = serialize_options(options)

        assert isinstance(result, dict)
        assert isinstance(result["inner"], dict)
        assert result["inner"]["value"] == 10

    def test_serializes_path_to_string(self):
        """Converts Path objects to strings."""

        @dataclasses.dataclass
        class OptionsWithPath:
            path: Path = Path("/tmp/test")

        options = OptionsWithPath()
        result = serialize_options(options)

        assert isinstance(result["path"], str)
        assert result["path"] == "/tmp/test"

    def test_serializes_lists(self):
        """Serializes list fields."""

        @dataclasses.dataclass
        class OptionsWithList:
            items: list = dataclasses.field(default_factory=lambda: [1, 2, 3])

        options = OptionsWithList()
        result = serialize_options(options)

        assert isinstance(result["items"], list)
        assert result["items"] == [1, 2, 3]

    def test_serializes_dicts(self):
        """Serializes dict fields."""

        @dataclasses.dataclass
        class OptionsWithDict:
            metadata: dict = dataclasses.field(default_factory=lambda: {"key": "value"})

        options = OptionsWithDict()
        result = serialize_options(options)

        assert isinstance(result["metadata"], dict)
        assert result["metadata"]["key"] == "value"

    def test_handles_none_values(self):
        """Handles None values correctly."""

        @dataclasses.dataclass
        class OptionsWithNone:
            optional: str | None = None

        options = OptionsWithNone()
        result = serialize_options(options)

        assert result["optional"] is None

    def test_handles_primitive_types(self):
        """Handles primitive types (str, int, float, bool)."""

        @dataclasses.dataclass
        class PrimitiveOptions:
            name: str = "test"
            count: int = 42
            ratio: float = 3.14
            flag: bool = False

        options = PrimitiveOptions()
        result = serialize_options(options)

        assert result["name"] == "test"
        assert result["count"] == 42
        assert result["ratio"] == 3.14
        assert result["flag"] is False
