"""Tests for Pydantic type models."""

from __future__ import annotations

import pytest

from undetecta.types import (
    Action,
    ActionType,
    BrandingButtonStyle,
    BrandingColors,
    BrandingComponents,
    BrandingConfidence,
    BrandingImages,
    BrandingProfile,
    BrandingSpacing,
    BrandingTypography,
    HealthResponse,
    ImageSearchResult,
    JobStatus,
    NewsSearchResult,
    ScrapeFormat,
    ScrapeJobError,
    ScrapeJobResponse,
    ScrapeMetadata,
    ScrapeOptions,
    ScreenshotClip,
    ScreenshotOptions,
    ScreenshotViewport,
    ScrapedMetadata,
    SearchCategory,
    SearchEngine,
    SearchJobError,
    SearchJobResponse,
    SearchOptions,
    SearchScrapeOptions,
    SearchSource,
    WaitUntil,
    WebSearchResult,
)


class TestEnums:
    """Tests for enum types."""

    def test_scrape_format_enum(self):
        """Test ScrapeFormat enum values."""
        assert ScrapeFormat.HTML.value == "html"
        assert ScrapeFormat.RAW_HTML.value == "rawHtml"
        assert ScrapeFormat.MARKDOWN.value == "markdown"
        assert ScrapeFormat.LINKS.value == "links"
        assert ScrapeFormat.SCREENSHOT.value == "screenshot"
        assert ScrapeFormat.BRANDING.value == "branding"

    def test_wait_until_enum(self):
        """Test WaitUntil enum values."""
        assert WaitUntil.LOAD.value == "load"
        assert WaitUntil.DOM_CONTENT_LOADED.value == "domcontentloaded"
        assert WaitUntil.NETWORK_IDLE.value == "networkidle"

    def test_job_status_enum(self):
        """Test JobStatus enum values."""
        assert JobStatus.PENDING.value == "pending"
        assert JobStatus.RUNNING.value == "running"
        assert JobStatus.COMPLETED.value == "completed"
        assert JobStatus.FAILED.value == "failed"
        assert JobStatus.STOPPED.value == "stopped"

    def test_action_type_enum(self):
        """Test ActionType enum values."""
        assert ActionType.CLICK.value == "click"
        assert ActionType.FILL.value == "fill"
        assert ActionType.SELECT.value == "select"
        assert ActionType.WAIT.value == "wait"
        assert ActionType.SCROLL.value == "scroll"

    def test_search_source_enum(self):
        """Test SearchSource enum values."""
        assert SearchSource.WEB.value == "web"
        assert SearchSource.NEWS.value == "news"
        assert SearchSource.IMAGES.value == "images"

    def test_search_engine_enum(self):
        """Test SearchEngine enum values."""
        assert SearchEngine.GOOGLE.value == "google"
        assert SearchEngine.DUCK_DUCK_GO.value == "duckduckgo"

    def test_search_category_enum(self):
        """Test SearchCategory enum values."""
        assert SearchCategory.GITHUB.value == "github"
        assert SearchCategory.RESEARCH.value == "research"
        assert SearchCategory.PDF.value == "pdf"


class TestScreenshotOptions:
    """Tests for screenshot-related models."""

    def test_screenshot_clip(self):
        """Test ScreenshotClip model."""
        clip = ScreenshotClip(x=0, y=0, width=1920, height=1080)
        assert clip.x == 0
        assert clip.y == 0
        assert clip.width == 1920
        assert clip.height == 1080
        assert clip.scale is None

    def test_screenshot_clip_with_scale(self):
        """Test ScreenshotClip with scale."""
        clip = ScreenshotClip(x=0, y=0, width=1920, height=1080, scale=2.0)
        assert clip.scale == 2.0

    def test_screenshot_viewport(self):
        """Test ScreenshotViewport model."""
        viewport = ScreenshotViewport(width=1920, height=1080)
        assert viewport.width == 1920
        assert viewport.height == 1080
        assert viewport.device_scale_factor is None

    def test_screenshot_viewport_with_device_scale(self):
        """Test ScreenshotViewport with device scale factor using alias."""
        # When deserializing from API response (camelCase)
        data = {"width": 1920, "height": 1080, "deviceScaleFactor": 2.0}
        viewport = ScreenshotViewport(**data)
        assert viewport.device_scale_factor == 2.0

    def test_screenshot_options(self):
        """Test ScreenshotOptions model using alias for deserialization."""
        # When deserializing from API response (camelCase)
        data = {"fullPage": True, "format": "png"}
        options = ScreenshotOptions(**data)
        assert options.full_page is True
        assert options.format == "png"
        assert options.quality is None

    def test_screenshot_options_all_fields(self):
        """Test ScreenshotOptions with all fields using aliases."""
        clip = ScreenshotClip(x=0, y=0, width=100, height=100)
        viewport_data = {"width": 1920, "height": 1080, "deviceScaleFactor": 2.0}
        viewport = ScreenshotViewport(**viewport_data)

        # When deserializing from API response (camelCase)
        data = {
            "fullPage": True,
            "format": "jpeg",
            "quality": 90,
            "clip": clip.model_dump(),
            "selector": ".main",
            "viewport": viewport.model_dump(by_alias=True),
            "optimizeForSpeed": True,
        }
        options = ScreenshotOptions(**data)
        assert options.full_page is True
        assert options.format == "jpeg"
        assert options.quality == 90
        assert options.clip == clip
        assert options.selector == ".main"
        assert options.viewport.device_scale_factor == 2.0
        assert options.optimize_for_speed is True


class TestAction:
    """Tests for Action model."""

    def test_click_action(self):
        """Test click action."""
        action = Action(type=ActionType.CLICK, selector=".button")
        assert action.type == ActionType.CLICK
        assert action.selector == ".button"
        assert action.value is None

    def test_fill_action(self):
        """Test fill action."""
        action = Action(type=ActionType.FILL, selector="input[name='email']", value="test@example.com")
        assert action.type == ActionType.FILL
        assert action.value == "test@example.com"

    def test_wait_action(self):
        """Test wait action."""
        action = Action(type=ActionType.WAIT, options={"duration": 1000})
        assert action.type == ActionType.WAIT
        assert action.options == {"duration": 1000}


class TestScrapeMetadata:
    """Tests for ScrapeMetadata model."""

    def test_minimal_metadata(self):
        """Test metadata with minimal fields."""
        metadata = ScrapeMetadata(status_code=200, url="https://example.com")
        assert metadata.status_code == 200
        assert metadata.url == "https://example.com"
        assert metadata.title is None

    def test_full_metadata(self):
        """Test metadata with all fields."""
        metadata = ScrapeMetadata(
            status_code=200,
            url="https://example.com",
            title="Example",
            description="An example site",
            language="en",
            og_title="OG Title",
            og_image="https://example.com/og.png",
        )
        assert metadata.status_code == 200
        assert metadata.title == "Example"
        assert metadata.og_title == "OG Title"


class TestBrandingModels:
    """Tests for branding-related models."""

    def test_branding_colors(self):
        """Test BrandingColors model."""
        colors = BrandingColors(primary="#ff0000", secondary="#00ff00")
        assert colors.primary == "#ff0000"
        assert colors.secondary == "#00ff00"

    def test_branding_typography(self):
        """Test BrandingTypography model."""
        typography = BrandingTypography(
            font_families={"heading": "Arial", "body": "Georgia"},
            font_sizes={"heading": "24px", "body": "16px"},
        )
        assert typography.font_families["heading"] == "Arial"
        assert typography.font_sizes["body"] == "16px"

    def test_branding_button_style(self):
        """Test BrandingButtonStyle model."""
        button = BrandingButtonStyle(
            background_color="#000000",
            text_color="#ffffff",
            border_radius="4px",
        )
        assert button.background_color == "#000000"
        assert button.text_color == "#ffffff"
        assert button.border_radius == "4px"

    def test_branding_confidence(self):
        """Test BrandingConfidence model."""
        confidence = BrandingConfidence(buttons=0.85, colors=0.90, overall=0.88)
        assert confidence.buttons == 0.85
        assert confidence.colors == 0.90
        assert confidence.overall == 0.88

    def test_branding_profile(self):
        """Test BrandingProfile model."""
        colors = BrandingColors(primary="#ff0000")
        confidence = BrandingConfidence(buttons=0.8, colors=0.9, overall=0.85)
        profile = BrandingProfile(
            color_scheme="light",
            colors=colors,
            confidence=confidence,
        )
        assert profile.color_scheme == "light"
        assert profile.colors.primary == "#ff0000"
        assert profile.confidence.overall == 0.85


class TestScrapeJobResponse:
    """Tests for ScrapeJobResponse model."""

    def test_completed_scrape_job(self):
        """Test completed scrape job."""
        job = ScrapeJobResponse(
            id="job-123",
            status=JobStatus.COMPLETED,
            created_at="2024-01-01T00:00:00Z",
            completed_at="2024-01-01T00:00:05Z",
            markdown="# Content",
        )
        assert job.id == "job-123"
        assert job.status == JobStatus.COMPLETED
        assert job.markdown == "# Content"
        assert job.completed_at == "2024-01-01T00:00:05Z"

    def test_failed_scrape_job(self):
        """Test failed scrape job with error."""
        error = ScrapeJobError(code="SCRAPER_ERROR", message="Failed to load page")
        job = ScrapeJobResponse(
            id="job-456",
            status=JobStatus.FAILED,
            created_at="2024-01-01T00:00:00Z",
            error=error,
        )
        assert job.status == JobStatus.FAILED
        assert job.error.code == "SCRAPER_ERROR"
        assert job.error.message == "Failed to load page"

    def test_scrape_job_with_all_formats(self):
        """Test scrape job with all output formats."""
        metadata = ScrapeMetadata(status_code=200, url="https://example.com", title="Example")
        job = ScrapeJobResponse(
            id="job-789",
            status=JobStatus.COMPLETED,
            created_at="2024-01-01T00:00:00Z",
            metadata=metadata,
            html="<html>...</html>",
            markdown="# Example",
            links=["https://example.com/page1", "https://example.com/page2"],
            screenshot="base64encoded",
        )
        assert job.metadata.title == "Example"
        assert job.html == "<html>...</html>"
        assert len(job.links) == 2
        assert job.screenshot == "base64encoded"


class TestScrapeOptions:
    """Tests for ScrapeOptions model."""

    def test_minimal_options(self):
        """Test minimal scrape options."""
        options = ScrapeOptions(url="https://example.com")
        assert options.url == "https://example.com"
        assert options.formats is None

    def test_options_with_formats(self):
        """Test options with output formats."""
        options = ScrapeOptions(
            url="https://example.com",
            formats=[ScrapeFormat.MARKDOWN, ScrapeFormat.LINKS],
        )
        assert len(options.formats) == 2
        assert ScrapeFormat.MARKDOWN in options.formats

    def test_options_with_browser_settings(self):
        """Test options with browser settings."""
        options = ScrapeOptions(
            url="https://example.com",
            headless=True,
            proxy="http://proxy.example.com:8080",
            mobile=True,
        )
        assert options.headless is True
        assert options.proxy == "http://proxy.example.com:8080"
        assert options.mobile is True

    def test_options_with_wait_settings(self):
        """Test options with wait settings."""
        options = ScrapeOptions(
            url="https://example.com",
            wait_for=1000,
            wait_until=WaitUntil.NETWORK_IDLE,
            wait_for_selector=".content",
        )
        assert options.wait_for == 1000
        assert options.wait_until == WaitUntil.NETWORK_IDLE
        assert options.wait_for_selector == ".content"

    def test_options_with_actions(self):
        """Test options with browser actions."""
        actions = [
            Action(type=ActionType.CLICK, selector=".cookie-accept"),
            Action(type=ActionType.WAIT, options={"duration": 500}),
        ]
        options = ScrapeOptions(url="https://example.com", actions=actions)
        assert len(options.actions) == 2
        assert options.actions[0].type == ActionType.CLICK


class TestSearchResults:
    """Tests for search result models."""

    def test_web_search_result(self):
        """Test WebSearchResult model."""
        result = WebSearchResult(
            url="https://example.com",
            title="Example Site",
            description="An example website",
            position=1,
        )
        assert result.url == "https://example.com"
        assert result.title == "Example Site"
        assert result.description == "An example website"
        assert result.position == 1
        assert result.markdown is None

    def test_web_search_result_with_scraped_content(self):
        """Test WebSearchResult with scraped content."""
        metadata = ScrapedMetadata(title="Example", status_code=200)
        result = WebSearchResult(
            url="https://example.com",
            title="Example Site",
            description="An example website",
            markdown="# Scraped content",
            metadata=metadata,
        )
        assert result.markdown == "# Scraped content"
        assert result.metadata.title == "Example"

    def test_news_search_result(self):
        """Test NewsSearchResult model."""
        result = NewsSearchResult(
            url="https://example.com/news",
            title="Breaking News",
            snippet="Something happened",
            date="2024-01-01",
        )
        assert result.url == "https://example.com/news"
        assert result.title == "Breaking News"
        assert result.snippet == "Something happened"
        assert result.date == "2024-01-01"

    def test_image_search_result(self):
        """Test ImageSearchResult model."""
        result = ImageSearchResult(
            image_url="https://example.com/image.jpg",
            image_width=1920,
            image_height=1080,
            url="https://example.com",
            title="Image Title",
            position=1,
        )
        assert result.image_url == "https://example.com/image.jpg"
        assert result.image_width == 1920
        assert result.image_height == 1080
        assert result.position == 1


class TestSearchJobResponse:
    """Tests for SearchJobResponse model."""

    def test_completed_search_job(self):
        """Test completed search job."""
        job = SearchJobResponse(
            id="search-123",
            status=JobStatus.COMPLETED,
            created_at="2024-01-01T00:00:00Z",
            web=[
                WebSearchResult(
                    url="https://example.com",
                    title="Example",
                    description="A site",
                )
            ],
        )
        assert job.id == "search-123"
        assert job.status == JobStatus.COMPLETED
        assert len(job.web) == 1
        assert job.web[0].title == "Example"

    def test_search_job_with_multiple_sources(self):
        """Test search job with multiple sources."""
        job = SearchJobResponse(
            id="search-456",
            status=JobStatus.COMPLETED,
            created_at="2024-01-01T00:00:00Z",
            web=[
                WebSearchResult(url="https://example.com/1", title="Result 1", description="Desc 1")
            ],
            news=[
                NewsSearchResult(
                    url="https://news.example.com",
                    title="News",
                    snippet="News snippet",
                )
            ],
            images=[
                ImageSearchResult(
                    image_url="https://example.com/img.jpg",
                    position=1,
                )
            ],
        )
        assert len(job.web) == 1
        assert len(job.news) == 1
        assert len(job.images) == 1


class TestSearchOptions:
    """Tests for SearchOptions model."""

    def test_minimal_options(self):
        """Test minimal search options."""
        options = SearchOptions(query="test query")
        assert options.query == "test query"
        assert options.limit is None

    def test_options_with_engine_and_limit(self):
        """Test options with engine and limit."""
        options = SearchOptions(
            query="test query",
            engine=SearchEngine.GOOGLE,
            limit=20,
        )
        assert options.engine == SearchEngine.GOOGLE
        assert options.limit == 20

    def test_options_with_sources(self):
        """Test options with multiple sources."""
        options = SearchOptions(
            query="test query",
            sources=[SearchSource.WEB, SearchSource.NEWS],
        )
        assert len(options.sources) == 2
        assert SearchSource.NEWS in options.sources

    def test_options_with_localization(self):
        """Test options with localization settings."""
        options = SearchOptions(
            query="test query",
            lang="en",
            country="US",
            location="New York, NY",
        )
        assert options.lang == "en"
        assert options.country == "US"
        assert options.location == "New York, NY"

    def test_options_with_scrape_options(self):
        """Test options with result scraping."""
        scrape_options = SearchScrapeOptions(
            formats=[ScrapeFormat.MARKDOWN],
            only_main_content=True,
        )
        options = SearchOptions(
            query="test query",
            scrape_options=scrape_options,
        )
        assert options.scrape_options is not None
        assert options.scrape_options.only_main_content is True


class TestSearchScrapeOptions:
    """Tests for SearchScrapeOptions model."""

    def test_minimal_scrape_options(self):
        """Test minimal scrape options."""
        options = SearchScrapeOptions()
        assert options.formats is None

    def test_scrape_options_with_formats(self):
        """Test scrape options with formats."""
        options = SearchScrapeOptions(
            formats=[ScrapeFormat.MARKDOWN, ScrapeFormat.LINKS],
            timeout=30000,
        )
        assert len(options.formats) == 2
        assert options.timeout == 30000


class TestHealthResponse:
    """Tests for HealthResponse model."""

    def test_health_response(self):
        """Test health response."""
        health = HealthResponse(
            status="ok",
            timestamp="2024-01-01T00:00:00Z",
        )
        assert health.status == "ok"
        assert health.timestamp == "2024-01-01T00:00:00Z"


class TestModelSerialization:
    """Tests for model serialization/deserialization."""

    def test_scrape_options_serialization(self):
        """Test ScrapeOptions serialization with aliases."""
        options = ScrapeOptions(
            url="https://example.com",
            formats=[ScrapeFormat.MARKDOWN],
            wait_for=1000,
            block_images=True,
        )
        data = options.model_dump(by_alias=True, exclude_none=True)
        assert data["url"] == "https://example.com"
        assert data["formats"] == ["markdown"]
        assert data["waitFor"] == 1000
        assert data["blockImages"] is True

    def test_scrape_options_deserialization(self):
        """Test ScrapeOptions deserialization with aliases."""
        data = {
            "url": "https://example.com",
            "formats": ["markdown"],
            "waitFor": 1000,
            "blockImages": True,
        }
        options = ScrapeOptions(**data)
        assert options.url == "https://example.com"
        assert options.wait_for == 1000
        assert options.block_images is True

    def test_search_job_response_deserialization(self):
        """Test SearchJobResponse deserialization with aliases."""
        data = {
            "id": "job-123",
            "status": "completed",
            "createdAt": "2024-01-01T00:00:00Z",
            "completedAt": "2024-01-01T00:00:05Z",
            "web": [
                {
                    "url": "https://example.com",
                    "title": "Example",
                    "description": "A site",
                }
            ],
        }
        response = SearchJobResponse(**data)
        assert response.id == "job-123"
        assert response.created_at == "2024-01-01T00:00:00Z"
        assert response.completed_at == "2024-01-01T00:00:05Z"
        assert len(response.web) == 1
