"""Fuzzy card matching service."""
from typing import List, Tuple, Optional
from thefuzz import fuzz


class FuzzyCardMatcher:
    """Fuzzy matching service for card names."""

    # Thresholds
    EXACT_MATCH_THRESHOLD = 100
    AUTO_ACCEPT_THRESHOLD = 85  # Auto-accept matches above this
    MANUAL_REVIEW_THRESHOLD = 70  # Flag for manual review below this
    MIN_MATCH_THRESHOLD = 60  # Minimum similarity to consider a match

    @staticmethod
    def normalize_card_name(name: str) -> str:
        """
        Normalize a card name for matching.
        
        Args:
            name: Raw card name
            
        Returns:
            Normalized card name
        """
        # Remove extra whitespace
        normalized = ' '.join(name.split())
        # Convert to lowercase for matching
        return normalized.lower()

    @staticmethod
    def exact_match(name: str, card_name: str) -> bool:
        """Check if two card names match exactly."""
        return FuzzyCardMatcher.normalize_card_name(name) == FuzzyCardMatcher.normalize_card_name(card_name)

    @staticmethod
    def fuzzy_match(name: str, card_name: str) -> float:
        """
        Calculate fuzzy match score between two card names.
        
        Args:
            name: First card name
            card_name: Second card name
            
        Returns:
            Similarity score between 0.0 and 100.0
        """
        normalized_name = FuzzyCardMatcher.normalize_card_name(name)
        normalized_card = FuzzyCardMatcher.normalize_card_name(card_name)
        return fuzz.token_sort_ratio(normalized_name, normalized_card)

    @staticmethod
    def find_best_match(
        card_name: str,
        candidate_names: List[str],
        threshold: float = MANUAL_REVIEW_THRESHOLD
    ) -> Tuple[Optional[str], float, str]:
        """
        Find the best matching card name from candidates.
        
        Args:
            card_name: Name to match
            candidate_names: List of candidate card names
            threshold: Minimum similarity threshold
            
        Returns:
            Tuple of (matched_name, confidence, match_type)
            - matched_name: Best matching card name or None
            - confidence: Match confidence (0.0 to 1.0)
            - match_type: 'exact', 'high_confidence', 'low_confidence', or 'no_match'
        """
        if not candidate_names:
            return None, 0.0, 'no_match'

        # Check for exact match first
        for candidate in candidate_names:
            if FuzzyCardMatcher.exact_match(card_name, candidate):
                return candidate, 1.0, 'exact'

        # Use fuzzy matching
        normalized_name = FuzzyCardMatcher.normalize_card_name(card_name)
        
        # Find best match using token sort ratio
        best_match = None
        best_score = 0.0
        
        for candidate in candidate_names:
            score = fuzz.token_sort_ratio(normalized_name, FuzzyCardMatcher.normalize_card_name(candidate))
            if score > best_score:
                best_score = score
                best_match = candidate

        if best_match and best_score >= threshold:
            confidence = best_score / 100.0
            if best_score >= FuzzyCardMatcher.AUTO_ACCEPT_THRESHOLD:
                match_type = 'high_confidence'
            else:
                match_type = 'low_confidence'
            return best_match, confidence, match_type

        return None, 0.0, 'no_match'

    @staticmethod
    def batch_match(
        card_names: List[str],
        candidate_names: List[str],
        threshold: float = MANUAL_REVIEW_THRESHOLD
    ) -> List[Tuple[str, Optional[str], float, str]]:
        """
        Perform batch fuzzy matching.
        
        Args:
            card_names: List of card names to match
            candidate_names: List of candidate card names
            threshold: Minimum similarity threshold
            
        Returns:
            List of tuples: (original_name, matched_name, confidence, match_type)
        """
        results = []
        for card_name in card_names:
            matched_name, confidence, match_type = FuzzyCardMatcher.find_best_match(
                card_name, candidate_names, threshold
            )
            results.append((card_name, matched_name, confidence, match_type))
        return results

    @staticmethod
    def batch_match_with_database(
        card_names: List[str],
        db_session,
        mtgonline_card_model,
        threshold: float = MANUAL_REVIEW_THRESHOLD
    ) -> List[Tuple[str, Optional[int], Optional[str], float, str]]:
        """
        Perform batch fuzzy matching against database cards.
        
        Args:
            card_names: List of card names to match
            db_session: Database session
            mtgonline_card_model: MtgonlineCard ORM model
            threshold: Minimum similarity threshold
            
        Returns:
            List of tuples: (original_name, card_id, matched_name, confidence, match_type)
        """
        from sqlalchemy import select
        
        # Fetch all cards from database
        stmt = select(mtgonline_card_model)
        result = db_session.execute(stmt)
        db_cards = result.scalars().all()
        
        # Build candidate list and lookup
        candidate_names = [card.name for card in db_cards if card.name]
        card_lookup = {card.name.lower(): card for card in db_cards if card.name}
        
        results = []
        for card_name in card_names:
            matched_name, confidence, match_type = FuzzyCardMatcher.find_best_match(
                card_name, candidate_names, threshold
            )
            
            card_id = None
            if matched_name and matched_name.lower() in card_lookup:
                card_id = card_lookup[matched_name.lower()].id
            
            results.append((card_name, card_id, matched_name, confidence, match_type))
        
        return results
