"""
Utility functions for OpenCompletion Activity System v2.0

Features:
- Template variable rendering ({{metadata.key}}, {{current_attempt}}, etc.)
- Advanced metadata conditions (gte, lt, contains, regex, etc.)
- Conditional content blocks (show_if)
- Conditional navigation (if/elif/else)
- Weighted random selection
- Progressive hints
"""

import re
import random
from typing import Any, Dict, List, Optional, Union


def render_template(text: str, context: Dict[str, Any]) -> str:
    """
    Render template variables in text using {{variable}} syntax.

    Supports:
    - {{metadata.key}} - Access metadata values
    - {{current_attempt}} - Current attempt number
    - {{max_attempts}} - Maximum attempts
    - {{attempts_remaining}} - Remaining attempts
    - {{current_section}} - Current section ID
    - {{current_step}} - Current step ID
    - {{username}} - Last responding username

    Args:
        text: Text containing {{variable}} templates
        context: Dictionary with metadata, attempts, section/step info

    Returns:
        Text with variables replaced
    """
    if not isinstance(text, str):
        return text

    # Find all {{variable}} patterns
    pattern = r"\{\{([^}]+)\}\}"

    def replace_variable(match):
        var_name = match.group(1).strip()

        # Handle metadata.key syntax
        if var_name.startswith("metadata."):
            key = var_name[9:]  # Remove 'metadata.' prefix
            metadata = context.get("metadata", {})
            value = metadata.get(
                key, f"{{{{metadata.{key}}}}}"
            )  # Keep original if not found
            return str(value) if value is not None else ""

        # Handle built-in variables
        value = context.get(
            var_name, f"{{{{{var_name}}}}}"
        )  # Keep original if not found
        return str(value) if value is not None else ""

    return re.sub(pattern, replace_variable, text)


def evaluate_condition(
    metadata: Dict[str, Any], condition_key: str, condition_value: Any
) -> bool:
    """
    Evaluate a single condition against metadata.

    Supports operators:
    - key: value - Equality
    - key_ne: value - Not equal
    - key_gt: value - Greater than
    - key_gte: value - Greater than or equal
    - key_lt: value - Less than
    - key_lte: value - Less than or equal
    - key_between: [min, max] - Between (inclusive)
    - key_contains: value - Comma-separated list contains value
    - key_not_contains: value - List does NOT contain value
    - key_matches: pattern - Regex match
    - key_exists: true/false - Key existence check
    - key_not_exists: true/false - Key non-existence check

    Args:
        metadata: Metadata dictionary to check
        condition_key: Condition key (may have operator suffix)
        condition_value: Expected value

    Returns:
        True if condition met, False otherwise
    """
    # Check for operator suffixes
    if condition_key.endswith("_ne"):
        key = condition_key[:-3]
        return metadata.get(key) != condition_value

    elif condition_key.endswith("_gt"):
        key = condition_key[:-3]
        try:
            return float(metadata.get(key, 0)) > float(condition_value)
        except (ValueError, TypeError):
            return False

    elif condition_key.endswith("_gte"):
        key = condition_key[:-4]
        try:
            return float(metadata.get(key, 0)) >= float(condition_value)
        except (ValueError, TypeError):
            return False

    elif condition_key.endswith("_lt"):
        key = condition_key[:-3]
        try:
            return float(metadata.get(key, 0)) < float(condition_value)
        except (ValueError, TypeError):
            return False

    elif condition_key.endswith("_lte"):
        key = condition_key[:-4]
        try:
            return float(metadata.get(key, 0)) <= float(condition_value)
        except (ValueError, TypeError):
            return False

    elif condition_key.endswith("_between"):
        key = condition_key[:-8]
        if not isinstance(condition_value, list) or len(condition_value) != 2:
            return False
        try:
            val = float(metadata.get(key, 0))
            return float(condition_value[0]) <= val <= float(condition_value[1])
        except (ValueError, TypeError):
            return False

    elif condition_key.endswith("_not_contains"):
        key = condition_key[:-13]
        value_str = str(metadata.get(key, ""))
        items = [item.strip() for item in value_str.split(",") if item.strip()]
        return str(condition_value) not in items

    elif condition_key.endswith("_contains"):
        key = condition_key[:-9]
        value_str = str(metadata.get(key, ""))
        # Split by comma and check if condition_value is in list
        items = [item.strip() for item in value_str.split(",") if item.strip()]
        return str(condition_value) in items

    elif condition_key.endswith("_matches"):
        key = condition_key[:-8]
        value_str = str(metadata.get(key, ""))
        try:
            return bool(re.search(str(condition_value), value_str))
        except re.error:
            return False

    elif condition_key.endswith("_not_exists"):
        key = condition_key[:-11]
        if condition_value:
            return key not in metadata
        else:
            return key in metadata

    elif condition_key.endswith("_exists"):
        key = condition_key[:-7]
        if condition_value:
            return key in metadata
        else:
            return key not in metadata

    else:
        # Simple equality check
        return metadata.get(condition_key) == condition_value


def check_conditions(metadata: Dict[str, Any], conditions: Dict[str, Any]) -> bool:
    """
    Check if ALL conditions are met (AND logic).

    Args:
        metadata: Metadata dictionary
        conditions: Dictionary of condition_key: condition_value pairs

    Returns:
        True if all conditions met, False otherwise
    """
    if not conditions:
        return True

    return all(
        evaluate_condition(metadata, key, value) for key, value in conditions.items()
    )


def filter_content_blocks(
    content_blocks: List[Union[str, Dict[str, Any]]],
    metadata: Dict[str, Any],
    context: Dict[str, Any],
) -> List[str]:
    """
    Filter and render content blocks based on show_if conditions.

    Content blocks can be:
    - Simple strings: Always shown
    - Objects with 'text' and 'show_if': Conditionally shown

    Args:
        content_blocks: List of content blocks (strings or dicts)
        metadata: Metadata dictionary for condition evaluation
        context: Template rendering context

    Returns:
        List of rendered text strings that passed conditions
    """
    result = []

    for block in content_blocks:
        if isinstance(block, str):
            # Simple string - always show, just render templates
            rendered = render_template(block, context)
            result.append(rendered)

        elif isinstance(block, dict):
            # Conditional block - check show_if condition
            text = block.get("text", "")
            show_if = block.get("show_if", {})

            # Check if conditions are met
            if check_conditions(metadata, show_if):
                rendered = render_template(text, context)
                result.append(rendered)

    return result


def resolve_conditional_navigation(
    next_section_and_step: Union[str, List[Dict[str, Any]]], metadata: Dict[str, Any]
) -> Optional[str]:
    """
    Resolve conditional navigation (if/elif/else structure).

    Args:
        next_section_and_step: Either a string or list of conditional branches
        metadata: Metadata dictionary for condition evaluation

    Returns:
        Resolved "section:step" string or None
    """
    # Simple string - return as-is
    if isinstance(next_section_and_step, str):
        return next_section_and_step

    # Conditional branches
    if isinstance(next_section_and_step, list):
        for branch in next_section_and_step:
            if "if" in branch:
                # if branch
                if check_conditions(metadata, branch["if"]):
                    return branch.get("goto")

            elif "elif" in branch:
                # elif branch
                if check_conditions(metadata, branch["elif"]):
                    return branch.get("goto")

            elif "else" in branch:
                # else branch - always taken if reached
                return branch.get("goto")

    return None


def select_weighted_random(weighted_options: List[Dict[str, Any]]) -> Any:
    """
    Select a random value from weighted options.

    Args:
        weighted_options: List of dicts with 'value' and 'weight' keys

    Returns:
        Selected value
    """
    if not weighted_options:
        return None

    # Extract values and weights
    values = [opt["value"] for opt in weighted_options]
    weights = [opt.get("weight", 1) for opt in weighted_options]

    # Use random.choices for weighted selection
    selected = random.choices(values, weights=weights, k=1)
    return selected[0]


def get_progressive_hint(
    hints: List[Dict[str, Any]], current_attempt: int, context: Dict[str, Any]
) -> Optional[Dict[str, Any]]:
    """
    Get the hint for the current attempt number, if one exists.

    Args:
        hints: List of hint dicts with 'attempt', 'text', 'counts_as_attempt' keys
        current_attempt: Current attempt number (1, 2, 3, ...)
        context: Template rendering context

    Returns:
        Hint dict with rendered text, or None if no hint for this attempt
    """
    if not hints:
        return None

    for hint in hints:
        if hint.get("attempt") == current_attempt:
            # Render template variables in hint text
            hint_text = render_template(hint.get("text", ""), context)
            return {
                "text": hint_text,
                "counts_as_attempt": hint.get("counts_as_attempt", False),
            }

    return None


def create_template_context(
    metadata: Dict[str, Any],
    current_attempt: int,
    max_attempts: int,
    current_section: str,
    current_step: str,
    username: str = "User",
) -> Dict[str, Any]:
    """
    Create a template rendering context with all built-in variables.

    Args:
        metadata: Activity metadata
        current_attempt: Current attempt number
        max_attempts: Maximum attempts allowed
        current_section: Current section ID
        current_step: Current step ID
        username: Username of last responder

    Returns:
        Context dictionary for template rendering
    """
    return {
        "metadata": metadata,
        "current_attempt": current_attempt,
        "max_attempts": max_attempts,
        "attempts_remaining": max(0, max_attempts - current_attempt),
        "current_section": current_section,
        "current_step": current_step,
        "username": username,
    }
