"""
Data extraction utilities for extracting structured data from HTML pages.

This module provides functions to extract data from the final page using
CSS selectors, element attributes, and optional regex patterns.
"""

import re
from typing import Any

from playwright.async_api import Error as PlaywrightError
from playwright.async_api import FrameLocator, Page
from playwright.async_api import TimeoutError as PlaywrightTimeoutError

from .exceptions import DataExtractionError
from .models import DataExtractionConfig, ExtractionFieldDefinition
from .utils import setup_logger

logger = setup_logger(__name__)


def _apply_regex(value: str | None, pattern: str | None) -> str | None:
    """
    Apply regex pattern to extracted value if provided.

    Args:
        value: Raw extracted string value
        pattern: Regex pattern with optional capture group

    Returns:
        First capture group or full match if pattern provided, stripped value otherwise

    """
    if value is None:
        return None
    if pattern is None:
        return value.strip()

    match = re.search(pattern=pattern, string=value)
    if match is None:
        return None

    if match.groups():
        return match.group(1)
    return match.group(0)


async def extract_field_value(
    page: Page,
    field_name: str,
    field_def: ExtractionFieldDefinition,
) -> str | list[str] | None:
    """
    Extract a single field value from the page.

    Args:
        page: Playwright page instance
        field_name: Name of the field being extracted (for error context)
        field_def: Extraction field definition

    Returns:
        Extracted value(s) - string for single match, list for multiple matches,
        None if no matches found and multiple=False

    """
    try:
        context: Page | FrameLocator = (
            page.frame_locator(selector=field_def.iframe_selector)
            if field_def.iframe_selector is not None
            else page
        )
        locator = context.locator(field_def.selector)
        count = await locator.count()

        if count == 0:
            if field_def.multiple:
                return []
            return None

        if field_def.multiple:
            raw_values: list[str] = []
            for i in range(count):
                element = locator.nth(index=i)
                raw_value = (
                    await element.get_attribute(name=field_def.attribute)
                    if field_def.attribute
                    else await element.text_content()
                )
                if raw_value is not None:
                    processed = _apply_regex(value=raw_value, pattern=field_def.regex)
                    if processed is not None:
                        raw_values.append(processed)
            return raw_values

        element = locator.first
        raw_value = (
            await element.get_attribute(name=field_def.attribute)
            if field_def.attribute
            else await element.text_content()
        )
        if raw_value is None:
            return None
        return _apply_regex(value=raw_value, pattern=field_def.regex)

    except (PlaywrightTimeoutError, PlaywrightError) as e:
        raise DataExtractionError(
            field_name=field_name,
            selector=field_def.selector,
            reason=str(e),
        ) from e


async def extract_data(
    page: Page,
    config: DataExtractionConfig,
) -> dict[str, Any]:
    """
    Extract all configured data fields from the page.

    Args:
        page: Playwright page instance
        config: Data extraction configuration

    Returns:
        Dictionary mapping field names to extracted values

    """
    extracted: dict[str, Any] = {}

    for field_name, field_def in config.fields.items():
        value = await extract_field_value(
            page=page,
            field_name=field_name,
            field_def=field_def,
        )
        extracted[field_name] = value
        result_count = len(value) if isinstance(value, list) else (0 if value is None else 1)
        logger.info(
            f"Extracted field: name='{field_name}' selector='{field_def.selector}' "
            f"multiple={field_def.multiple} result_count={result_count}"
        )

    return extracted
