# src/fmatch/engine_algorithm_integration.py
"""
Integration module to connect algorithm selection rules with the main engine.
"""

import logging
from typing import Dict, List, Optional, Any

from fmatch.algorithm_selection.rule_engine import (
    get_algorithm_selection_engine,
)

log = logging.getLogger(__name__)


def create_column_profile_for_algorithm_selection(
    column_stats: Dict[str, Any], exotic_profile: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
    """
    Create a unified column profile for algorithm selection from various sources.

    Args:
        column_stats: Basic column statistics (from validation, etc.)
        exotic_profile: Exotic data detection results

    Returns:
        Unified profile dictionary for rule evaluation
    """
    profile = {
        # Basic stats
        "distinct_ratio": column_stats.get("unique_ratio", 0),
        "avg_length": column_stats.get("avg_length", 0),
        "null_ratio": column_stats.get("null_ratio", 0),
        "entropy": column_stats.get("entropy", 0),
        "gini_coefficient": column_stats.get("gini_coefficient", 0),
        # Semantic type
        "semantic_type": column_stats.get("semantic_type"),
        # Pattern flags
        "has_numeric_pattern": column_stats.get("has_numeric_pattern", False),
        "has_email_pattern": column_stats.get("has_email_pattern", False),
        "has_phone_pattern": column_stats.get("has_phone_pattern", False),
        "has_url_pattern": column_stats.get("has_url_pattern", False),
        "has_date_pattern": column_stats.get("has_date_pattern", False),
    }

    # Add exotic profile data if available
    if exotic_profile:
        profile.update(
            {
                "script": exotic_profile.get("script", "unknown"),
                "script_confidence": exotic_profile.get("script_confidence", 0),
                "has_emoji": exotic_profile.get("has_emoji", False),
                "encoding_issues": exotic_profile.get("encoding_issues", False),
                "mixed_types": exotic_profile.get("mixed_types", False),
                "special_chars_ratio": exotic_profile.get("special_chars_ratio", 0),
            }
        )

    return profile


def evaluate_algorithms_for_columns(
    column_profiles: Dict[str, Dict[str, Any]],
    available_algorithms: Optional[List[str]] = None,
) -> Dict[str, List[tuple[str, float]]]:
    """
    Evaluate algorithms for multiple columns.

    Args:
        column_profiles: Dictionary mapping column names to their profiles
        available_algorithms: List of algorithms to consider

    Returns:
        Dictionary mapping column names to lists of (algorithm, score) tuples
    """
    engine = get_algorithm_selection_engine()
    results = {}

    for column_name, profile in column_profiles.items():
        algorithm_scores = engine.select_algorithms(profile, available_algorithms)
        results[column_name] = algorithm_scores

        # Log top recommendations
        if algorithm_scores:
            top_3 = algorithm_scores[:3]
            log.info(
                f"Algorithm recommendations for '{column_name}': "
                f"{', '.join(f'{algo}({score:.2f})' for algo, score in top_3)}"
            )

    return results


def get_consensus_algorithm(
    column_algorithm_scores: Dict[str, List[tuple[str, float]]],
    weights: Optional[Dict[str, float]] = None,
) -> str:
    """
    Get consensus algorithm across multiple columns.

    Args:
        column_algorithm_scores: Results from evaluate_algorithms_for_columns
        weights: Optional column weights

    Returns:
        Best consensus algorithm
    """
    if not column_algorithm_scores:
        return "WRatio"  # Default fallback

    # Aggregate scores across columns
    algorithm_totals: Dict[str, float] = {}

    for column, algo_scores in column_algorithm_scores.items():
        column_weight = weights.get(column, 1.0) if weights else 1.0

        for algo, score in algo_scores:
            if algo not in algorithm_totals:
                algorithm_totals[algo] = 0
            algorithm_totals[algo] += score * column_weight

    # Find best
    best_algo = max(algorithm_totals.items(), key=lambda x: x[1])

    log.info(
        f"Consensus algorithm selection: {best_algo[0]} (score: {best_algo[1]:.2f})"
    )

    return best_algo[0]


# Modify the existing _determine_optimal_blocking_strategy to use rule-based selection
def enhance_strategy_with_algorithm_rules(
    strategy: Dict[str, Any],
    validated_candidates: List[Dict[str, Any]],
    mappings: List[Dict[str, str]],
) -> Dict[str, Any]:
    """
    Enhance a blocking strategy with rule-based algorithm selection.

    This would be called from _determine_optimal_blocking_strategy after
    the blocking column is selected but before returning.
    """
    # Collect profiles for mapped columns
    column_profiles = {}
    column_weights = {}

    for mapping in mappings:
        src_col = mapping.get("source")
        weight = mapping.get("weight", 1.0)

        if src_col:
            # Find validation info for this column
            for candidate in validated_candidates:
                if candidate["src_col"] == src_col:
                    metrics = candidate.get("src_metrics", {})
                    if metrics:
                        profile = create_column_profile_for_algorithm_selection(metrics)
                        column_profiles[src_col] = profile
                        column_weights[src_col] = weight
                    break

    if column_profiles:
        # Evaluate algorithms for each column
        algo_scores = evaluate_algorithms_for_columns(column_profiles)

        # Get consensus
        best_algorithm = get_consensus_algorithm(algo_scores, column_weights)

        # Update strategy
        strategy["recommended_algorithm"] = best_algorithm
        strategy["algorithm_scores"] = algo_scores

        log.info(
            f"Rule-based algorithm selection: {best_algorithm} "
            f"(based on {len(column_profiles)} column profiles)"
        )

    return strategy
