"""
VYNDR Similarity Engine
Find historically similar games for confidence adjustment.
Shared by NBA and MLB. Minimum similarity threshold 0.7.
"""

import logging

logger = logging.getLogger('vyndr')

MIN_SIMILARITY = 0.7

# Similarity factors and their relative importance
SIMILARITY_FACTORS = {
    # NBA factors
    'opponent_defensive_rating': 0.15,
    'pace': 0.12,
    'rest_days': 0.08,
    'home_away': 0.06,
    'functional_role_match': 0.15,
    'teammate_context': 0.10,
    # MLB factors
    'pitcher_handedness': 0.12,
    'park_factor': 0.10,
    'opponent_quality': 0.12,
    'weather_similarity': 0.05,
    'day_night': 0.04,
    'batting_order_position': 0.06,
}


def calculate_similarity_score(game_a, game_b, factors=None):
    """
    Calculate similarity score between two games.
    Uses weighted factor comparison with normalization.

    Args:
        game_a: Dict of game context factors.
        game_b: Dict of game context factors.
        factors: Optional dict of factor weights. Defaults to SIMILARITY_FACTORS.

    Returns:
        Float similarity score between 0.0 and 1.0.
    """
    if factors is None:
        factors = SIMILARITY_FACTORS

    total_score = 0.0
    total_weight = 0.0

    for factor, weight in factors.items():
        val_a = game_a.get(factor)
        val_b = game_b.get(factor)
        if val_a is None or val_b is None:
            continue

        # Boolean factors
        if isinstance(val_a, bool) or isinstance(val_b, bool):
            similarity = 1.0 if val_a == val_b else 0.0
        # String factors (categorical)
        elif isinstance(val_a, str) or isinstance(val_b, str):
            similarity = 1.0 if val_a == val_b else 0.0
        # Numeric factors
        else:
            max_val = max(abs(val_a), abs(val_b), 1)
            diff = abs(val_a - val_b) / max_val
            similarity = max(0.0, 1.0 - diff)

        total_score += similarity * weight
        total_weight += weight

    if total_weight == 0:
        return 0.0
    return min(1.0, max(0.0, total_score / total_weight))


def find_similar_games(target_game, historical_games, max_results=5, min_similarity=None):
    """
    Find historically similar games above the minimum similarity threshold.

    Args:
        target_game: Dict of current game context factors.
        historical_games: List of historical game dicts.
        max_results: Maximum number of similar games to return.
        min_similarity: Minimum similarity score threshold (default 0.7).

    Returns:
        List of (similarity_score, game) tuples, sorted by similarity descending.
        Only games at or above min_similarity are included.
    """
    if min_similarity is None:
        min_similarity = MIN_SIMILARITY

    scored = []
    for game in historical_games:
        score = calculate_similarity_score(target_game, game)
        if score >= min_similarity:
            scored.append((score, game))

    scored.sort(key=lambda x: x[0], reverse=True)
    return scored[:max_results]
