"""
Lightweight config classes for building crawl requests.

These are simplified versions of crawl4ai's BrowserConfig and CrawlerRunConfig.
They only handle serialization for the Docker API — no browser or crawling logic.
"""

from enum import Enum
from typing import Any, Dict, List, Optional, Union


class CacheMode(Enum):
    """Cache mode for crawl requests."""
    ENABLED = "enabled"
    DISABLED = "disabled"
    BYPASS = "bypass"
    WRITE_ONLY = "write_only"
    READ_ONLY = "read_only"


class _SerializableConfig:
    """Base class with dict serialization for the Docker API."""

    def dump(self) -> Dict[str, Any]:
        """Serialize to the format expected by the crawl4ai Docker API."""
        params = {}
        for key, value in self.__dict__.items():
            if key.startswith("_"):
                continue
            if value is None:
                continue
            if isinstance(value, _SerializableConfig):
                params[key] = value.dump()
            elif isinstance(value, Enum):
                params[key] = {"type": type(value).__name__, "params": value.value}
            elif isinstance(value, dict):
                params[key] = {"type": "dict", "value": value}
            elif isinstance(value, list):
                params[key] = [
                    item.dump() if isinstance(item, _SerializableConfig) else item
                    for item in value
                ]
            else:
                params[key] = value
        return {"type": type(self).__name__, "params": params}


class BrowserConfig(_SerializableConfig):
    """Browser configuration for crawl requests."""

    def __init__(
        self,
        headless: bool = True,
        browser_type: str = "chromium",
        viewport_width: int = 1080,
        viewport_height: int = 720,
        user_agent: Optional[str] = None,
        proxy: Optional[str] = None,
        headers: Optional[Dict[str, str]] = None,
        text_mode: bool = False,
        java_script_enabled: bool = True,
        ignore_https_errors: bool = True,
        accept_downloads: bool = False,
        **kwargs: Any,
    ):
        self.headless = headless
        self.browser_type = browser_type
        self.viewport_width = viewport_width
        self.viewport_height = viewport_height
        self.user_agent = user_agent
        self.proxy = proxy
        self.headers = headers
        self.text_mode = text_mode
        self.java_script_enabled = java_script_enabled
        self.ignore_https_errors = ignore_https_errors
        self.accept_downloads = accept_downloads
        for k, v in kwargs.items():
            setattr(self, k, v)


# ── Deep Crawl Strategies ──────────────────────────────────────────────

class BFSDeepCrawlStrategy(_SerializableConfig):
    """Breadth-First Search deep crawling strategy."""

    def __init__(
        self,
        max_depth: int = 2,
        max_pages: int = 10,
        include_external: bool = False,
        score_threshold: float = 0.0,
        **kwargs: Any,
    ):
        self.max_depth = max_depth
        self.max_pages = max_pages
        self.include_external = include_external
        self.score_threshold = score_threshold
        for k, v in kwargs.items():
            setattr(self, k, v)


class DFSDeepCrawlStrategy(_SerializableConfig):
    """Depth-First Search deep crawling strategy."""

    def __init__(
        self,
        max_depth: int = 2,
        max_pages: int = 10,
        include_external: bool = False,
        score_threshold: float = 0.0,
        **kwargs: Any,
    ):
        self.max_depth = max_depth
        self.max_pages = max_pages
        self.include_external = include_external
        self.score_threshold = score_threshold
        for k, v in kwargs.items():
            setattr(self, k, v)


class BestFirstCrawlingStrategy(_SerializableConfig):
    """Best-First deep crawling strategy with URL scoring."""

    def __init__(
        self,
        max_depth: int = 2,
        max_pages: int = 10,
        include_external: bool = False,
        score_threshold: float = 0.5,
        **kwargs: Any,
    ):
        self.max_depth = max_depth
        self.max_pages = max_pages
        self.include_external = include_external
        self.score_threshold = score_threshold
        for k, v in kwargs.items():
            setattr(self, k, v)


# ── Extraction Strategies ──────────────────────────────────────────────

class JsonCssExtractionStrategy(_SerializableConfig):
    """CSS-based JSON extraction strategy."""

    def __init__(self, schema: Dict[str, Any], **kwargs: Any):
        self.schema = schema
        for k, v in kwargs.items():
            setattr(self, k, v)

    def dump(self) -> Dict[str, Any]:
        # Schema needs special handling — pass through as-is, not as {"type": "dict"}
        params = {"schema": self.schema}
        for key, value in self.__dict__.items():
            if key in ("schema",) or key.startswith("_"):
                continue
            if value is not None:
                params[key] = value
        return {"type": "JsonCssExtractionStrategy", "params": params}


class LLMExtractionStrategy(_SerializableConfig):
    """LLM-based extraction strategy."""

    def __init__(
        self,
        provider: Optional[str] = None,
        api_token: Optional[str] = None,
        schema: Optional[str] = None,
        extraction_type: str = "block",
        instruction: Optional[str] = None,
        chunk_token_threshold: int = 1000,
        overlap_rate: float = 0.0,
        apply_chunking: bool = True,
        input_format: str = "markdown",
        **kwargs: Any,
    ):
        self.provider = provider
        self.api_token = api_token
        self.schema = schema
        self.extraction_type = extraction_type
        self.instruction = instruction
        self.chunk_token_threshold = chunk_token_threshold
        self.overlap_rate = overlap_rate
        self.apply_chunking = apply_chunking
        self.input_format = input_format
        for k, v in kwargs.items():
            setattr(self, k, v)


# ── Content Filters ────────────────────────────────────────────────────

class PruningContentFilter(_SerializableConfig):
    """Content filter using pruning algorithm."""

    def __init__(self, threshold: float = 0.48, **kwargs: Any):
        self.threshold = threshold
        for k, v in kwargs.items():
            setattr(self, k, v)


class BM25ContentFilter(_SerializableConfig):
    """Content filter using BM25 scoring."""

    def __init__(self, user_query: Optional[str] = None, **kwargs: Any):
        self.user_query = user_query
        for k, v in kwargs.items():
            setattr(self, k, v)


# ── Scraping Strategies ────────────────────────────────────────────────

class LXMLWebScrapingStrategy(_SerializableConfig):
    """Default LXML-based web scraping strategy."""

    def __init__(self, **kwargs: Any):
        for k, v in kwargs.items():
            setattr(self, k, v)


class PDFContentScrapingStrategy(_SerializableConfig):
    """PDF content scraping strategy."""

    def __init__(self, **kwargs: Any):
        for k, v in kwargs.items():
            setattr(self, k, v)


# ── Table Extraction ───────────────────────────────────────────────────

class DefaultTableExtraction(_SerializableConfig):
    """Default table extraction strategy."""

    def __init__(self, **kwargs: Any):
        for k, v in kwargs.items():
            setattr(self, k, v)


class NoTableExtraction(_SerializableConfig):
    """Disable table extraction."""

    def __init__(self, **kwargs: Any):
        for k, v in kwargs.items():
            setattr(self, k, v)


# ── Other Configs ──────────────────────────────────────────────────────

class ProxyConfig(_SerializableConfig):
    """Proxy configuration."""

    def __init__(
        self,
        server: str = "",
        username: Optional[str] = None,
        password: Optional[str] = None,
        **kwargs: Any,
    ):
        self.server = server
        self.username = username
        self.password = password
        for k, v in kwargs.items():
            setattr(self, k, v)


class GeolocationConfig(_SerializableConfig):
    """Geolocation configuration."""

    def __init__(
        self,
        latitude: float = 0.0,
        longitude: float = 0.0,
        accuracy: float = 10.0,
        **kwargs: Any,
    ):
        self.latitude = latitude
        self.longitude = longitude
        self.accuracy = accuracy
        for k, v in kwargs.items():
            setattr(self, k, v)


# ── Crawler Run Config ─────────────────────────────────────────────────

class CrawlerRunConfig(_SerializableConfig):
    """Crawler run configuration for crawl requests."""

    def __init__(
        self,
        # Content selection
        cache_mode: Optional[CacheMode] = None,
        css_selector: Optional[str] = None,
        excluded_selector: Optional[str] = None,
        target_elements: Optional[List[str]] = None,
        excluded_tags: Optional[List[str]] = None,
        word_count_threshold: int = 200,
        # Output
        verbose: bool = True,
        stream: bool = False,
        screenshot: bool = False,
        screenshot_wait_for: Optional[float] = None,
        pdf: bool = False,
        scan_full_page: bool = False,
        capture_network_requests: bool = False,
        capture_console_messages: bool = False,
        # Links
        exclude_external_links: bool = False,
        exclude_social_media_links: bool = False,
        # Page interaction
        wait_for: Optional[str] = None,
        wait_until: Optional[str] = None,
        wait_for_images: bool = False,
        delay_before_return_html: float = 0.1,
        page_timeout: Optional[int] = None,
        js_code: Optional[List[str]] = None,
        js_code_before_wait: Optional[List[str]] = None,
        remove_overlay_elements: bool = False,
        # Session
        session_id: Optional[str] = None,
        # Strategies
        deep_crawl_strategy: Optional[_SerializableConfig] = None,
        extraction_strategy: Optional[_SerializableConfig] = None,
        scraping_strategy: Optional[_SerializableConfig] = None,
        table_extraction: Optional[_SerializableConfig] = None,
        # Locale / geo
        locale: Optional[str] = None,
        timezone_id: Optional[str] = None,
        geolocation: Optional[_SerializableConfig] = None,
        # Proxy
        proxy_config: Optional[_SerializableConfig] = None,
        # Matching (for crawler_configs list)
        url_matcher: Optional[str] = None,
        # Forward compat
        **kwargs: Any,
    ):
        self.cache_mode = cache_mode
        self.css_selector = css_selector
        self.excluded_selector = excluded_selector
        self.target_elements = target_elements
        self.excluded_tags = excluded_tags
        self.word_count_threshold = word_count_threshold
        self.verbose = verbose
        self.stream = stream
        self.screenshot = screenshot
        self.screenshot_wait_for = screenshot_wait_for
        self.pdf = pdf
        self.scan_full_page = scan_full_page
        self.capture_network_requests = capture_network_requests
        self.capture_console_messages = capture_console_messages
        self.exclude_external_links = exclude_external_links
        self.exclude_social_media_links = exclude_social_media_links
        self.wait_for = wait_for
        self.wait_until = wait_until
        self.wait_for_images = wait_for_images
        self.delay_before_return_html = delay_before_return_html
        self.page_timeout = page_timeout
        self.js_code = js_code
        self.js_code_before_wait = js_code_before_wait
        self.remove_overlay_elements = remove_overlay_elements
        self.session_id = session_id
        self.deep_crawl_strategy = deep_crawl_strategy
        self.extraction_strategy = extraction_strategy
        self.scraping_strategy = scraping_strategy
        self.table_extraction = table_extraction
        self.locale = locale
        self.timezone_id = timezone_id
        self.geolocation = geolocation
        self.proxy_config = proxy_config
        self.url_matcher = url_matcher
        for k, v in kwargs.items():
            setattr(self, k, v)
