"""Detect variables in agent history for reuse"""

import re

from noesium.subagents.bu.agent.views import AgentHistoryList, DetectedVariable
from noesium.subagents.bu.dom.views import DOMInteractedElement


def detect_variables_in_history(history: AgentHistoryList) -> dict[str, DetectedVariable]:
    """
    Analyze agent history and detect reusable variables.

    Uses two strategies:
    1. Element attributes (id, name, type, placeholder, aria-label) - most reliable
    2. Value pattern matching (email, phone, date formats) - fallback

    Returns:
            Dictionary mapping variable names to DetectedVariable objects
    """
    detected: dict[str, DetectedVariable] = {}
    detected_values: set[str] = set()  # Track which values we've already detected

    for step_idx, history_item in enumerate(history.history):
        if not history_item.model_output:
            continue

        for action_idx, action in enumerate(history_item.model_output.action):
            # Convert action to dict - handle both Pydantic models and dict-like objects
            if hasattr(action, "model_dump"):
                action_dict = action.model_dump()
            elif isinstance(action, dict):
                action_dict = action
            else:
                # For SimpleNamespace or similar objects
                action_dict = vars(action)

            # Get the interacted element for this action (if available)
            element = None
            if history_item.state and history_item.state.interacted_element:
                if len(history_item.state.interacted_element) > action_idx:
                    element = history_item.state.interacted_element[action_idx]

            # Detect variables in this action
            _detect_in_action(action_dict, element, detected, detected_values)

    return detected


def _detect_in_action(
    action_dict: dict,
    element: DOMInteractedElement | None,
    detected: dict[str, DetectedVariable],
    detected_values: set[str],
) -> None:
    """Detect variables in a single action using element context"""

    # Extract action type and parameters
    for action_type, params in action_dict.items():
        if not isinstance(params, dict):
            continue

        # Check fields that commonly contain variables
        fields_to_check = ["text", "query"]

        for field in fields_to_check:
            if field not in params:
                continue

            value = params[field]
            if not isinstance(value, str) or not value.strip():
                continue

            # Skip if we already detected this exact value
            if value in detected_values:
                continue

            # Try to detect variable type (with element context)
            var_info = _detect_variable_type(value, element)
            if not var_info:
                continue

            var_name, var_format = var_info

            # Ensure unique variable name
            var_name = _ensure_unique_name(var_name, detected)

            # Add detected variable
            detected[var_name] = DetectedVariable(
                name=var_name,
                original_value=value,
                type="string",
                format=var_format,
            )

            detected_values.add(value)


def _detect_variable_type(
    value: str,
    element: DOMInteractedElement | None = None,
) -> tuple[str, str | None] | None:
    """
    Detect if a value looks like a variable, using element context when available.

    Priority:
    1. Element attributes (id, name, type, placeholder, aria-label) - most reliable
    2. Value pattern matching (email, phone, date formats) - fallback

    Returns:
            (variable_name, format) or None if not detected
    """

    # STRATEGY 1: Use element attributes (most reliable)
    if element and element.attributes:
        attr_detection = _detect_from_attributes(element.attributes)
        if attr_detection:
            return attr_detection

    # STRATEGY 2: Pattern matching on value (fallback)
    return _detect_from_value_pattern(value)


def _detect_from_attributes(attributes: dict[str, str]) -> tuple[str, str | None] | None:
    """
    Detect variable from element attributes.

    Check attributes in priority order:
    1. type attribute (HTML5 input types - most specific)
    2. id, name, placeholder, aria-label (semantic hints)
    """

    # Check 'type' attribute first (HTML5 input types)
    input_type = attributes.get("type", "").lower()
    if input_type == "email":
        return ("email", "email")
    elif input_type == "tel":
        return ("phone", "phone")
    elif input_type == "date":
        return ("date", "date")
    elif input_type == "number":
        return ("number", "number")
    elif input_type == "url":
        return ("url", "url")

    # Combine semantic attributes for keyword matching
    semantic_attrs = [
        attributes.get("id", ""),
        attributes.get("name", ""),
        attributes.get("placeholder", ""),
        attributes.get("aria-label", ""),
    ]

    combined_text = " ".join(semantic_attrs).lower()

    # Address detection
    if any(keyword in combined_text for keyword in ["address", "street", "addr"]):
        if "billing" in combined_text:
            return ("billing_address", None)
        elif "shipping" in combined_text:
            return ("shipping_address", None)
        else:
            return ("address", None)

    # Comment/Note detection
    if any(keyword in combined_text for keyword in ["comment", "note", "message", "description"]):
        return ("comment", None)

    # Email detection
    if "email" in combined_text or "e-mail" in combined_text:
        return ("email", "email")

    # Phone detection
    if any(keyword in combined_text for keyword in ["phone", "tel", "mobile", "cell"]):
        return ("phone", "phone")

    # Name detection (order matters - check specific before general)
    if "first" in combined_text and "name" in combined_text:
        return ("first_name", None)
    elif "last" in combined_text and "name" in combined_text:
        return ("last_name", None)
    elif "full" in combined_text and "name" in combined_text:
        return ("full_name", None)
    elif "name" in combined_text:
        return ("name", None)

    # Date detection
    if any(keyword in combined_text for keyword in ["date", "dob", "birth"]):
        return ("date", "date")

    # City detection
    if "city" in combined_text:
        return ("city", None)

    # State/Province detection
    if "state" in combined_text or "province" in combined_text:
        return ("state", None)

    # Country detection
    if "country" in combined_text:
        return ("country", None)

    # Zip code detection
    if any(keyword in combined_text for keyword in ["zip", "postal", "postcode"]):
        return ("zip_code", "postal_code")

    # Company detection
    if "company" in combined_text or "organization" in combined_text:
        return ("company", None)

    return None


def _detect_from_value_pattern(value: str) -> tuple[str, str | None] | None:
    """
    Detect variable type from value pattern (fallback when no element context).

    Patterns:
    - Email: contains @ and . with valid format
    - Phone: digits with separators, 10+ chars
    - Date: YYYY-MM-DD format
    - Name: Capitalized word(s), 2-30 chars, letters only
    - Number: Pure digits, 1-9 chars
    """

    # Email detection - most specific first
    if "@" in value and "." in value:
        # Basic email validation
        if re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", value):
            return ("email", "email")

    # Phone detection (digits with separators, 10+ chars)
    if re.match(r"^[\d\s\-\(\)\+]+$", value):
        # Remove separators and check length
        digits_only = re.sub(r"[\s\-\(\)\+]", "", value)
        if len(digits_only) >= 10:
            return ("phone", "phone")

    # Date detection (YYYY-MM-DD or similar)
    if re.match(r"^\d{4}-\d{2}-\d{2}$", value):
        return ("date", "date")

    # Name detection (capitalized, only letters/spaces, 2-30 chars)
    if value and value[0].isupper() and value.replace(" ", "").replace("-", "").isalpha() and 2 <= len(value) <= 30:
        words = value.split()
        if len(words) == 1:
            return ("first_name", None)
        elif len(words) == 2:
            return ("full_name", None)
        else:
            return ("name", None)

    # Number detection (pure digits, not phone length)
    if value.isdigit() and 1 <= len(value) <= 9:
        return ("number", "number")

    return None


def _ensure_unique_name(base_name: str, existing: dict[str, DetectedVariable]) -> str:
    """
    Ensure variable name is unique by adding suffix if needed.

    Examples:
            first_name → first_name
            first_name (exists) → first_name_2
            first_name_2 (exists) → first_name_3
    """
    if base_name not in existing:
        return base_name

    # Add numeric suffix
    counter = 2
    while f"{base_name}_{counter}" in existing:
        counter += 1

    return f"{base_name}_{counter}"
