"""
Direct PostgreSQL database access for MusicBrainz Data Cleaner.
Provides fuzzy search capabilities for artists and recordings.
"""

import psycopg2
import psycopg2.extras
from typing import List, Dict, Optional, Tuple, Any
from fuzzywuzzy import fuzz
from ..config.constants import (
    DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD, DB_CONNECTION_TIMEOUT,
    FUZZY_SEARCH_THRESHOLD, MAX_SEARCH_RESULTS,
    TITLE_SIMILARITY_THRESHOLD, ARTIST_SIMILARITY_THRESHOLD,
    SUCCESS_MESSAGES, ERROR_MESSAGES
)
from ..utils.artist_title_processing import parse_complex_collaboration, parse_collaborators, generate_title_variations
from ..core.interfaces import MusicBrainzDataProvider


class MusicBrainzDatabase(MusicBrainzDataProvider):
    """Direct PostgreSQL database access for MusicBrainz with fuzzy search."""
    
    def __init__(self, host: str = DB_HOST, port: int = DB_PORT, 
                 database: str = DB_NAME, user: str = DB_USER, 
                 password: str = DB_PASSWORD):
        self.host = host
        self.port = port
        self.database = database
        self.user = user
        self.password = password
        self.connection = None
        self.cursor = None
    
    def connect(self) -> bool:
        """Establish connection to the MusicBrainz database."""
        try:
            # Use the direct connection method that works
            self.connection = psycopg2.connect(
                host='db',  # Use Docker service name
                port=self.port,
                database=self.database,
                user=self.user,
                password=self.password,
                connect_timeout=DB_CONNECTION_TIMEOUT
            )
            self.cursor = self.connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
            print(SUCCESS_MESSAGES['db_connected'])
            return True
            
        except Exception as e:
            print(ERROR_MESSAGES['db_connection_failed'])
            print(f"Connection error: {e}")
            return False
    
    def disconnect(self):
        """Close database connection."""
        if self.cursor:
            self.cursor.close()
        if self.connection:
            self.connection.close()
    
    def __enter__(self):
        """Context manager entry."""
        self.connect()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit."""
        self.disconnect()
    
    def fuzzy_search_artist(self, artist_name: str) -> Optional[Tuple[str, str, float]]:
        """
        Fuzzy search for artist by name.
        Returns (artist_name, mbid, similarity_score) or None.
        """
        if not self.connection:
            return None
        
        try:
            # Clean artist name
            clean_name = artist_name.strip()
            
            # Handle collaborations - extract main artist
            if 'ft.' in clean_name.lower():
                main_artist = clean_name.split('ft.')[0].strip()
            elif 'feat.' in clean_name.lower():
                main_artist = clean_name.split('feat.')[0].strip()
            else:
                main_artist = clean_name
            
            # Try exact match first - but check for multiple matches
            query = "SELECT name, gid FROM artist WHERE name = %s"
            self.cursor.execute(query, (main_artist,))
            results = self.cursor.fetchall()
            
            if results:
                if len(results) == 1:
                    # Only one exact match, use it
                    result = results[0]
                    print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                        original=artist_name,
                        matched=result['name'],
                        score=1.0
                    ))
                    return (result['name'], result['gid'], 1.0)
                else:
                    # Multiple exact matches - choose the one with most recordings
                    print(f"🔍 Found {len(results)} exact matches for '{main_artist}', checking recording counts...")
                    artists_with_counts = []
                    for artist in results:
                        query = """
                            SELECT COUNT(r.id) as recording_count
                            FROM artist a
                            JOIN artist_credit_name acn ON a.id = acn.artist
                            JOIN artist_credit ac ON acn.artist_credit = ac.id
                            JOIN recording r ON ac.id = r.artist_credit
                            WHERE a.gid = %s
                        """
                        self.cursor.execute(query, (artist['gid'],))
                        count_result = self.cursor.fetchone()
                        recording_count = count_result['recording_count'] if count_result else 0
                        artists_with_counts.append({
                            'name': artist['name'],
                            'gid': artist['gid'],
                            'recording_count': recording_count
                        })
                        print(f"  📊 {artist['name']} ({artist['gid'][:8]}...): {recording_count} recordings")
                    
                    # Choose the artist with the most recordings
                    best_artist = max(artists_with_counts, key=lambda x: x['recording_count'])
                    print(f"  🎯 Selected: {best_artist['name']} with {best_artist['recording_count']} recordings")
                    
                    print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                        original=artist_name,
                        matched=best_artist['name'],
                        score=1.0
                    ))
                    return (best_artist['name'], best_artist['gid'], 1.0)
            
            # Try case-insensitive match
            query = "SELECT name, gid FROM artist WHERE LOWER(name) = LOWER(%s) LIMIT 1"
            self.cursor.execute(query, (main_artist,))
            result = self.cursor.fetchone()
            if result:
                print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                    original=artist_name,
                    matched=result['name'],
                    score=0.99
                ))
                return (result['name'], result['gid'], 0.99)
            
            # Try matching with different dash characters
            # Replace regular dash with Unicode dash and vice versa
            dash_variants = [
                main_artist.replace('-', '‐'),  # Regular dash to Unicode dash
                main_artist.replace('‐', '-'),  # Unicode dash to regular dash
            ]
            
            for variant in dash_variants:
                if variant != main_artist:
                    query = "SELECT name, gid FROM artist WHERE name = %s LIMIT 1"
                    self.cursor.execute(query, (variant,))
                    result = self.cursor.fetchone()
                    if result:
                        print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                            original=artist_name,
                            matched=result['name'],
                            score=0.98
                        ))
                        return (result['name'], result['gid'], 0.98)
            
            # Try matching with "&" vs "and" variations
            if '&' in main_artist:
                and_variant = main_artist.replace('&', 'and')
                query = "SELECT name, gid FROM artist WHERE name = %s LIMIT 1"
                self.cursor.execute(query, (and_variant,))
                result = self.cursor.fetchone()
                if result:
                    print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                        original=artist_name,
                        matched=result['name'],
                        score=0.97
                    ))
                    return (result['name'], result['gid'], 0.97)
            elif ' and ' in main_artist.lower():
                ampersand_variant = main_artist.replace(' and ', ' & ')
                query = "SELECT name, gid FROM artist WHERE name = %s LIMIT 1"
                self.cursor.execute(query, (ampersand_variant,))
                result = self.cursor.fetchone()
                if result:
                    print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                        original=artist_name,
                        matched=result['name'],
                        score=0.97
                    ))
                    return (result['name'], result['gid'], 0.97)
            
            # Try fuzzy search
            # Use more specific patterns to avoid false matches
            # Also try different dash characters and name variations
            dash_variants = [
                main_artist,
                main_artist.replace('-', '‐'),  # Regular dash to Unicode dash
                main_artist.replace('‐', '-'),  # Unicode dash to regular dash
            ]
            
            # Add name variations for common cases like "S Club 7" -> "S Club"
            name_variants = []
            for variant in dash_variants:
                name_variants.append(variant)
                # Handle cases like "S Club 7" -> "S Club"
                if ' ' in variant:
                    parts = variant.split()
                    if len(parts) > 1:
                        # Try without the last part if it's a number (e.g., "S Club 7" -> "S Club")
                        if parts[-1].isdigit():
                            name_variants.append(' '.join(parts[:-1]))
            
            # First try exact matches with name variations
            for variant in name_variants:
                # Try exact match in artist names
                query = "SELECT name, gid FROM artist WHERE name = %s LIMIT 1"
                self.cursor.execute(query, (variant,))
                result = self.cursor.fetchone()
                if result:
                    print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                        original=artist_name,
                        matched=result['name'],
                        score=0.95
                    ))
                    return (result['name'], result['gid'], 0.95)
                
                            # Try exact match in artist aliases
            query = """
                SELECT a.name, a.gid
                FROM artist a
                JOIN artist_alias aa ON a.id = aa.artist
                WHERE aa.name = %s
                LIMIT 1
            """
            self.cursor.execute(query, (variant,))
            result = self.cursor.fetchone()
            if result:
                print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                    original=artist_name,
                    matched=result['name'],
                    score=0.94
                ))
                return (result['name'], result['gid'], 0.94)
            
            # Try exact match in sort_name field
            query = "SELECT name, gid FROM artist WHERE sort_name = %s LIMIT 1"
            self.cursor.execute(query, (variant,))
            result = self.cursor.fetchone()
            if result:
                print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                    original=artist_name,
                    matched=result['name'],
                    score=0.93
                ))
                return (result['name'], result['gid'], 0.93)
            
            # Then try starts-with patterns for better precision
            patterns = []
            for variant in name_variants:
                patterns.extend([
                    f"{variant}%",   # Starts with (most precise)
                    f" {variant} ",  # Word boundary
                    f"%{variant}%",  # Contains (least precise)
                ])
            
            all_artists = []
            for pattern in patterns:
                # Search in artist names
                query = "SELECT name, gid FROM artist WHERE name ILIKE %s LIMIT 20"
                self.cursor.execute(query, (pattern,))
                artists = self.cursor.fetchall()
                all_artists.extend(artists)
                
                # Search in artist aliases
                query = """
                    SELECT a.name, a.gid 
                    FROM artist a
                    JOIN artist_alias aa ON a.id = aa.artist
                    WHERE aa.name ILIKE %s
                    LIMIT 20
                """
                self.cursor.execute(query, (pattern,))
                artists = self.cursor.fetchall()
                all_artists.extend(artists)
                
                # Search in sort_name field
                query = "SELECT name, gid FROM artist WHERE sort_name ILIKE %s LIMIT 20"
                self.cursor.execute(query, (pattern,))
                artists = self.cursor.fetchall()
                all_artists.extend(artists)
            
            # Remove duplicates based on MBID
            seen_mbids = set()
            unique_artists = []
            for artist in all_artists:
                if artist['gid'] not in seen_mbids:
                    seen_mbids.add(artist['gid'])
                    unique_artists.append(artist)
            
            # Get recording counts for all artists to prioritize famous ones
            artists_with_counts = []
            print(f"🔍 Found {len(unique_artists)} unique artists, checking recording counts...")
            for artist in unique_artists:
                query = """
                    SELECT COUNT(r.id) as recording_count
                    FROM artist a
                    JOIN artist_credit_name acn ON a.id = acn.artist
                    JOIN artist_credit ac ON acn.artist_credit = ac.id
                    JOIN recording r ON ac.id = r.artist_credit
                    WHERE a.gid = %s
                """
                self.cursor.execute(query, (artist['gid'],))
                result = self.cursor.fetchone()
                recording_count = result['recording_count'] if result else 0
                artists_with_counts.append({
                    'name': artist['name'],
                    'gid': artist['gid'],
                    'recording_count': recording_count
                })
                print(f"  📊 {artist['name']} ({artist['gid'][:8]}...): {recording_count} recordings")
            
            best_match = None
            best_score = 0
            best_recording_count = 0
            
            for artist in artists_with_counts:
                score = fuzz.ratio(main_artist.lower(), artist['name'].lower())
                
                # Only apply strict filtering for very obvious false matches
                # like "Sleazy-E" vs "Eazy-E" where one is clearly a substring of the other
                if (main_artist.lower() in artist['name'].lower() and 
                    artist['name'].lower() != main_artist.lower() and
                    len(artist['name']) > len(main_artist) + 2):
                    # This is likely a false match, require very high score
                    if score < 90:
                        continue
                
                # Prioritize artists with more recordings (famous artists)
                # If scores are close (within 5 points), prefer the one with more recordings
                if (score > best_score or 
                    (score >= best_score - 5 and artist['recording_count'] > best_recording_count * 2)):
                    best_score = score
                    best_match = artist
                    best_recording_count = artist['recording_count']
                    print(f"  🎯 New best match: {artist['name']} (score: {score}, recordings: {artist['recording_count']})")
            
            if best_score >= 70:  # Lower threshold for better matching
                print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                    original=artist_name,
                    matched=best_match['name'],
                    score=best_score / 100.0
                ))
                return (best_match['name'], best_match['gid'], best_score / 100.0)
            
            return None
            
        except Exception as e:
            print(f"Error in fuzzy_search_artist: {e}")
            return None
    
    def fuzzy_search_recording(self, title: str, artist_mbid: Optional[str] = None) -> Optional[Tuple[str, str, float]]:
        """
        Fuzzy search for recording by title and optionally artist MBID.
        Returns (recording_name, mbid, similarity_score) or None.
        """
        if not self.connection:
            return None
        
        try:
            # Generate title variations using the helper method
            search_titles = generate_title_variations(title)
            
            if artist_mbid:
                # Try to find recording by artist and title with all variations
                for search_title in search_titles:
                    query = """
                        SELECT r.name, r.gid
                        FROM recording r
                        JOIN artist_credit_name acn ON r.artist_credit = acn.artist_credit
                        JOIN artist a ON acn.artist = a.id
                        WHERE a.gid = %s AND r.name ILIKE %s
                        LIMIT 1
                    """
                    self.cursor.execute(query, (artist_mbid, f"%{search_title}%"))
                    result = self.cursor.fetchone()
                    
                    if result:
                        return (result['name'], result['gid'], 1.0)
                
                # Try fuzzy search with all title variations
                query = """
                    SELECT r.name, r.gid
                    FROM recording r
                    JOIN artist_credit_name acn ON r.artist_credit = acn.artist_credit
                    JOIN artist a ON acn.artist = a.id
                    WHERE a.gid = %s
                    LIMIT 100
                """
                self.cursor.execute(query, (artist_mbid,))
                recordings = self.cursor.fetchall()
                
                best_match = None
                best_score = 0
                
                for recording in recordings:
                    for search_title in search_titles:
                        score = fuzz.ratio(search_title.lower(), recording['name'].lower())
                        if score > best_score:
                            best_score = score
                            best_match = recording
                
                if best_score >= 70:  # Lower threshold for better matching
                    return (best_match['name'], best_match['gid'], best_score / 100.0)
            else:
                # No artist constraint - search by title only with all variations
                for search_title in search_titles:
                    # Try exact match first (handle both apostrophe types)
                    search_title_curly = search_title.replace("'", "'")
                    query = "SELECT name, gid FROM recording WHERE name = %s OR name = %s LIMIT 1"
                    self.cursor.execute(query, (search_title, search_title_curly))
                    result = self.cursor.fetchone()
                    if result:
                        return (result['name'], result['gid'], 1.0)
                    
                    # Try case-insensitive match (handle both apostrophe types)
                    query = "SELECT name, gid FROM recording WHERE LOWER(name) = LOWER(%s) OR LOWER(name) = LOWER(%s) LIMIT 1"
                    self.cursor.execute(query, (search_title, search_title_curly))
                    result = self.cursor.fetchone()
                    if result:
                        return (result['name'], result['gid'], 0.99)
                
                # Try partial match with all variations
                best_match = None
                best_score = 0
                
                for search_title in search_titles:
                    # Handle both apostrophe types in partial search
                    search_title_curly = search_title.replace("'", "'")
                    query = "SELECT name, gid FROM recording WHERE name ILIKE %s OR name ILIKE %s LIMIT 50"
                    self.cursor.execute(query, (f"%{search_title}%", f"%{search_title_curly}%"))
                    recordings = self.cursor.fetchall()
                    
                    for recording in recordings:
                        score = fuzz.ratio(search_title.lower(), recording['name'].lower())
                        if score > best_score:
                            best_score = score
                            best_match = recording
                
                if best_score >= 70:  # Lower threshold for better matching
                    return (best_match['name'], best_match['gid'], best_score / 100.0)
            
            return None
            
        except Exception as e:
            print(f"Error in fuzzy_search_recording: {e}")
            return None
    
    def fuzzy_search_artist_with_recording(self, artist_name: str, recording_title: str) -> Optional[Tuple[str, str, float]]:
        """
        Fuzzy search for artist by name, prioritizing artists that have the specific recording.
        Returns (artist_name, mbid, similarity_score) or None.
        """
        if not self.connection:
            return None
        
        try:
            # Clean artist name
            clean_name = artist_name.strip()
            
            # Handle collaborations - extract main artist
            if 'ft.' in clean_name.lower():
                main_artist = clean_name.split('ft.')[0].strip()
            elif 'feat.' in clean_name.lower():
                main_artist = clean_name.split('feat.')[0].strip()
            else:
                main_artist = clean_name
            
            # Find all potential artists (including aliases)
            all_artists = []
            
            # 1. Exact matches in artist names
            query = "SELECT name, gid FROM artist WHERE name = %s"
            self.cursor.execute(query, (main_artist,))
            exact_results = self.cursor.fetchall()
            for result in exact_results:
                all_artists.append({
                    'name': result['name'],
                    'gid': result['gid'],
                    'match_type': 'exact_name',
                    'score': 1.0
                })
            
            # 2. Exact matches in artist aliases
            query = """
                SELECT a.name, a.gid
                FROM artist a
                JOIN artist_alias aa ON a.id = aa.artist
                WHERE aa.name = %s
            """
            self.cursor.execute(query, (main_artist,))
            alias_results = self.cursor.fetchall()
            for result in alias_results:
                all_artists.append({
                    'name': result['name'],
                    'gid': result['gid'],
                    'match_type': 'exact_alias',
                    'score': 0.95
                })
            
            # 3. Case-insensitive matches
            query = "SELECT name, gid FROM artist WHERE LOWER(name) = LOWER(%s)"
            self.cursor.execute(query, (main_artist,))
            case_results = self.cursor.fetchall()
            for result in case_results:
                # Avoid duplicates
                if not any(a['gid'] == result['gid'] for a in all_artists):
                    all_artists.append({
                        'name': result['name'],
                        'gid': result['gid'],
                        'match_type': 'case_insensitive',
                        'score': 0.99
                    })
            
            # 4. Fuzzy matches in names and aliases
            name_variants = [main_artist]
            # Add dash variants
            name_variants.extend([
                main_artist.replace('-', '‐'),
                main_artist.replace('‐', '-'),
            ])
            
            # Add name variations (e.g., "S Club 7" -> "S Club")
            for variant in name_variants[:]:  # Copy list to avoid modification during iteration
                if ' ' in variant:
                    parts = variant.split()
                    if len(parts) > 1 and parts[-1].isdigit():
                        name_variants.append(' '.join(parts[:-1]))
            
            # Search for fuzzy matches
            for variant in name_variants:
                # Search in artist names
                query = "SELECT name, gid FROM artist WHERE name ILIKE %s"
                self.cursor.execute(query, (f'%{variant}%',))
                fuzzy_results = self.cursor.fetchall()
                for result in fuzzy_results:
                    if not any(a['gid'] == result['gid'] for a in all_artists):
                        score = fuzz.ratio(main_artist.lower(), result['name'].lower()) / 100.0
                        if score >= 0.8:  # Only include good matches
                            all_artists.append({
                                'name': result['name'],
                                'gid': result['gid'],
                                'match_type': 'fuzzy_name',
                                'score': score
                            })
                
                # Search in artist aliases
                query = """
                    SELECT a.name, a.gid 
                    FROM artist a
                    JOIN artist_alias aa ON a.id = aa.artist
                    WHERE aa.name ILIKE %s
                """
                self.cursor.execute(query, (f'%{variant}%',))
                fuzzy_alias_results = self.cursor.fetchall()
                for result in fuzzy_alias_results:
                    if not any(a['gid'] == result['gid'] for a in all_artists):
                        score = fuzz.ratio(main_artist.lower(), result['name'].lower()) / 100.0
                        if score >= 0.8:  # Only include good matches
                            all_artists.append({
                                'name': result['name'],
                                'gid': result['gid'],
                                'match_type': 'fuzzy_alias',
                                'score': score
                            })
            
            if not all_artists:
                return None
            
            # Remove duplicates and get recording information
            unique_artists = []
            seen_mbids = set()
            for artist in all_artists:
                if artist['gid'] not in seen_mbids:
                    seen_mbids.add(artist['gid'])
                    unique_artists.append(artist)
            
            print(f"🔍 Found {len(unique_artists)} potential artists for '{main_artist}', checking for recording '{recording_title}'...")
            
            # Check which artists have the specific recording
            artists_with_recording = []
            for artist in unique_artists:
                # Check if this artist has the specific recording
                query = """
                    SELECT COUNT(r.id) as recording_count
                    FROM artist a
                    JOIN artist_credit_name acn ON a.id = acn.artist
                    JOIN artist_credit ac ON acn.artist_credit = ac.id
                    JOIN recording r ON ac.id = r.artist_credit
                    WHERE a.gid = %s AND r.name ILIKE %s
                """
                self.cursor.execute(query, (artist['gid'], f'%{recording_title}%'))
                result = self.cursor.fetchone()
                has_recording = result['recording_count'] > 0 if result else False
                
                # Also get total recording count
                query = """
                    SELECT COUNT(r.id) as recording_count
                    FROM artist a
                    JOIN artist_credit_name acn ON a.id = acn.artist
                    JOIN artist_credit ac ON acn.artist_credit = ac.id
                    JOIN recording r ON ac.id = r.artist_credit
                    WHERE a.gid = %s
                """
                self.cursor.execute(query, (artist['gid'],))
                total_result = self.cursor.fetchone()
                total_recordings = total_result['recording_count'] if total_result else 0
                
                artist_info = {
                    **artist,
                    'has_recording': has_recording,
                    'total_recordings': total_recordings
                }
                artists_with_recording.append(artist_info)
                
                status = "✅ HAS RECORDING" if has_recording else "❌ NO RECORDING"
                print(f"  📊 {artist['name']} ({artist['gid'][:8]}...): {total_recordings} recordings - {status}")
            
            # Prioritize artists that have the specific recording
            artists_with_recording.sort(key=lambda x: (
                not x['has_recording'],  # Artists with recording first
                -x['score'],            # Then by score (highest first)
                -x['total_recordings']  # Then by total recordings (highest first)
            ))
            
            if artists_with_recording:
                best_artist = artists_with_recording[0]
                print(f"  🎯 Selected: {best_artist['name']} (score: {best_artist['score']:.2f}, has recording: {best_artist['has_recording']})")
                
                print(SUCCESS_MESSAGES['fuzzy_match_found'].format(
                    original=artist_name,
                    matched=best_artist['name'],
                    score=best_artist['score']
                ))
                return (best_artist['name'], best_artist['gid'], best_artist['score'])
            
            return None
            
        except Exception as e:
            print(f"Error in fuzzy_search_artist_with_recording: {e}")
            return None
    
    def fuzzy_search_artist_candidates(self, artist_name: str, recording_title: str = None) -> List[Dict[str, Any]]:
        """
        Fuzzy search for artist by name, returning multiple candidates.
        If recording_title is provided, prioritizes artists that have the specific recording.
        Returns list of artist candidates with their details.
        """
        if not self.connection:
            return []
        
        try:
            # Clean artist name
            clean_name = artist_name.strip()
            
            # Handle collaborations - extract main artist
            if 'ft.' in clean_name.lower():
                main_artist = clean_name.split('ft.')[0].strip()
            elif 'feat.' in clean_name.lower():
                main_artist = clean_name.split('feat.')[0].strip()
            else:
                main_artist = clean_name
            
            # Find all potential artists (including aliases)
            all_artists = []
            
            # 1. Exact matches in artist names
            query = "SELECT name, gid FROM artist WHERE name = %s"
            self.cursor.execute(query, (main_artist,))
            exact_results = self.cursor.fetchall()
            for result in exact_results:
                all_artists.append({
                    'name': result['name'],
                    'gid': result['gid'],
                    'match_type': 'exact_name',
                    'score': 1.0
                })
            
            # 2. Exact matches in artist aliases
            query = """
                SELECT a.name, a.gid
                FROM artist a
                JOIN artist_alias aa ON a.id = aa.artist
                WHERE aa.name = %s
            """
            self.cursor.execute(query, (main_artist,))
            alias_results = self.cursor.fetchall()
            for result in alias_results:
                all_artists.append({
                    'name': result['name'],
                    'gid': result['gid'],
                    'match_type': 'exact_alias',
                    'score': 0.95
                })
            
            # 3. Case-insensitive matches
            query = "SELECT name, gid FROM artist WHERE LOWER(name) = LOWER(%s)"
            self.cursor.execute(query, (main_artist,))
            case_results = self.cursor.fetchall()
            for result in case_results:
                # Avoid duplicates
                if not any(a['gid'] == result['gid'] for a in all_artists):
                    all_artists.append({
                        'name': result['name'],
                        'gid': result['gid'],
                        'match_type': 'case_insensitive',
                        'score': 0.99
                    })
            
            # 4. Fuzzy matches in names and aliases
            name_variants = [main_artist]
            # Add dash variants
            name_variants.extend([
                main_artist.replace('-', '‐'),
                main_artist.replace('‐', '-'),
            ])
            
            # Add name variations (e.g., "S Club 7" -> "S Club")
            for variant in name_variants[:]:  # Copy list to avoid modification during iteration
                if ' ' in variant:
                    parts = variant.split()
                    if len(parts) > 1 and parts[-1].isdigit():
                        name_variants.append(' '.join(parts[:-1]))
            
            # Search for fuzzy matches
            for variant in name_variants:
                # Search in artist names
                query = "SELECT name, gid FROM artist WHERE name ILIKE %s"
                self.cursor.execute(query, (f'%{variant}%',))
                fuzzy_results = self.cursor.fetchall()
                for result in fuzzy_results:
                    if not any(a['gid'] == result['gid'] for a in all_artists):
                        score = fuzz.ratio(main_artist.lower(), result['name'].lower()) / 100.0
                        if score >= 0.8:  # Only include good matches
                            all_artists.append({
                                'name': result['name'],
                                'gid': result['gid'],
                                'match_type': 'fuzzy_name',
                                'score': score
                            })
                
                # Search in artist aliases
                query = """
                    SELECT a.name, a.gid 
                    FROM artist a
                    JOIN artist_alias aa ON a.id = aa.artist
                    WHERE aa.name ILIKE %s
                """
                self.cursor.execute(query, (f'%{variant}%',))
                fuzzy_alias_results = self.cursor.fetchall()
                for result in fuzzy_alias_results:
                    if not any(a['gid'] == result['gid'] for a in all_artists):
                        score = fuzz.ratio(main_artist.lower(), result['name'].lower()) / 100.0
                        if score >= 0.8:  # Only include good matches
                            all_artists.append({
                                'name': result['name'],
                                'gid': result['gid'],
                                'match_type': 'fuzzy_alias',
                                'score': score
                            })
            
            if not all_artists:
                return []
            
            # Remove duplicates and get recording information
            unique_artists = []
            seen_mbids = set()
            for artist in all_artists:
                if artist['gid'] not in seen_mbids:
                    seen_mbids.add(artist['gid'])
                    unique_artists.append(artist)
            
            # If we have a recording title, check which artists have it
            if recording_title:
                print(f"🔍 Found {len(unique_artists)} potential artists for '{main_artist}', checking for recording '{recording_title}'...")
                
                # Check which artists have the specific recording
                for artist in unique_artists:
                    # Check if this artist has the specific recording
                    query = """
                        SELECT COUNT(r.id) as recording_count
                        FROM artist a
                        JOIN artist_credit_name acn ON a.id = acn.artist
                        JOIN artist_credit ac ON acn.artist_credit = ac.id
                        JOIN recording r ON ac.id = r.artist_credit
                        WHERE a.gid = %s AND r.name ILIKE %s
                    """
                    self.cursor.execute(query, (artist['gid'], f'%{recording_title}%'))
                    result = self.cursor.fetchone()
                    has_recording = result['recording_count'] > 0 if result else False
                    
                    # Also get total recording count
                    query = """
                        SELECT COUNT(r.id) as recording_count
                        FROM artist a
                        JOIN artist_credit_name acn ON a.id = acn.artist
                        JOIN artist_credit ac ON acn.artist_credit = ac.id
                        JOIN recording r ON ac.id = r.artist_credit
                        WHERE a.gid = %s
                    """
                    self.cursor.execute(query, (artist['gid'],))
                    total_result = self.cursor.fetchone()
                    total_recordings = total_result['recording_count'] if total_result else 0
                    
                    artist['has_recording'] = has_recording
                    artist['total_recordings'] = total_recordings
                    
                    status = "✅ HAS RECORDING" if has_recording else "❌ NO RECORDING"
                    print(f"  📊 {artist['name']} ({artist['gid'][:8]}...): {total_recordings} recordings - {status}")
                
                # Sort by: has recording first, then by score, then by total recordings
                unique_artists.sort(key=lambda x: (
                    not x.get('has_recording', False),  # Artists with recording first
                    -x['score'],                       # Then by score (highest first)
                    -x.get('total_recordings', 0)      # Then by total recordings (highest first)
                ))
            else:
                # Just get total recording counts for sorting
                for artist in unique_artists:
                    query = """
                        SELECT COUNT(r.id) as recording_count
                        FROM artist a
                        JOIN artist_credit_name acn ON a.id = acn.artist
                        JOIN artist_credit ac ON acn.artist_credit = ac.id
                        JOIN recording r ON ac.id = r.artist_credit
                        WHERE a.gid = %s
                    """
                    self.cursor.execute(query, (artist['gid'],))
                    total_result = self.cursor.fetchone()
                    total_recordings = total_result['recording_count'] if total_result else 0
                    artist['total_recordings'] = total_recordings
                
                # Sort by score and total recordings
                unique_artists.sort(key=lambda x: (-x['score'], -x['total_recordings']))
            
            return unique_artists
            
        except Exception as e:
            print(f"Error in fuzzy_search_artist_candidates: {e}")
            return []
    
    def get_artist_info(self, mbid: str) -> Optional[Dict[str, Any]]:
        """Get detailed artist information by MBID."""
        if not self.connection:
            return None
        
        try:
            query = """
                SELECT name, gid as mbid, sort_name
                FROM artist 
                WHERE gid = %s
            """
            self.cursor.execute(query, (mbid,))
            artist = self.cursor.fetchone()
            
            if artist:
                return dict(artist)
            return None
            
        except Exception as e:
            print(ERROR_MESSAGES['db_query_failed'].format(error=str(e)))
            return None
    
    def get_recording_info(self, mbid: str) -> Optional[Dict[str, Any]]:
        """Get recording information by MBID."""
        if not self.connection:
            return None
        
        try:
            query = """
                SELECT r.name as title, r.gid as mbid, ac.id as artist_credit_id
                FROM recording r
                JOIN artist_credit ac ON r.artist_credit = ac.id
                WHERE r.gid = %s
                LIMIT 1
            """
            self.cursor.execute(query, (mbid,))
            result = self.cursor.fetchone()
            
            if result:
                # Get artist credit information
                artist_query = """
                    SELECT a.name, acn.join_phrase, acn.position
                    FROM artist_credit_name acn
                    JOIN artist a ON acn.artist = a.id
                    WHERE acn.artist_credit = %s
                    ORDER BY acn.position
                """
                self.cursor.execute(artist_query, (result['artist_credit_id'],))
                artists = self.cursor.fetchall()
                
                # Build the artist string
                artist_string = ""
                for artist in artists:
                    artist_string += artist['name']
                    if artist['join_phrase']:
                        artist_string += artist['join_phrase']
                
                return {
                    'title': result['title'],
                    'mbid': result['mbid'],
                    'artist_credit': artist_string
                }
            
            return None
            
        except Exception as e:
            print(f"Error in get_recording_info: {e}")
            return None
    
    def test_connection(self) -> bool:
        """Test database connection."""
        try:
            if not self.connection:
                return self.connect()
            
            self.cursor.execute("SELECT 1")
            return True
        except Exception as e:
            print(f"Database connection test failed: {e}")
            return False

 