#!/usr/bin/env python3
"""
Command-line interface for MusicBrainz Data Cleaner.
Now uses direct database access with fuzzy search for better performance.
"""

import sys
import json
import time
import re
from pathlib import Path
from typing import Dict, Optional, Any, Tuple, List
from datetime import datetime

# Import constants
from ..config.constants import (
    DEFAULT_MUSICBRAINZ_URL,
    API_REQUEST_DELAY,
    PROGRESS_SEPARATOR,
    SUCCESS_MESSAGES,
    ERROR_MESSAGES,
    ExitCode
)

# Import data loader
from ..utils.data_loader import data_loader

# Import database and API clients
from ..api.database import MusicBrainzDatabase
from ..api.api_client import MusicBrainzAPIClient


class MusicBrainzCleaner:
    """Enhanced MusicBrainz Cleaner with direct database access and fuzzy search."""
    
    def __init__(self, use_database: bool = True, base_url: str = DEFAULT_MUSICBRAINZ_URL):
        self.use_database = use_database
        self.base_url = base_url
        
        # Initialize database connection (primary method)
        if use_database:
            self.db = MusicBrainzDatabase()
            if not self.db.connect():
                print("⚠️  Database connection failed, falling back to API")
                self.use_database = False
        
        # Initialize API client (fallback method)
        if not self.use_database:
            self.api = MusicBrainzAPIClient(base_url)
    
    def _clean_artist_name(self, artist_name: str) -> str:
        name = artist_name.strip()
        
        # Load name variations from data file
        variations = data_loader.load_name_variations()
        
        for old, new in variations.items():
            name = re.sub(rf'\b{re.escape(old)}\b', new, name, flags=re.IGNORECASE)
        
        return name
    
    def _clean_title(self, title: str) -> str:
        """
        Clean title by removing karaoke patterns.
        Note: The database search now tries multiple title variations including
        the full title and versions with parenthetical content removed.
        """
        title = title.strip()
        karaoke_patterns = [
            r'\s*\(Karaoke Version\)',
            r'\s*\(Karaoke\)',
            r'\s*\(Instrumental\)',
            r'\s*\(Backing Track\)',
        ]
        for pattern in karaoke_patterns:
            title = re.sub(pattern, '', title, flags=re.IGNORECASE)
        return title.strip()
    
    def find_artist_mbid(self, artist_name: str) -> Optional[str]:
        clean_name = self._clean_artist_name(artist_name)
        
        # Handle collaborations - prioritize finding artist credit
        # Use the same complex collaboration parsing as the database
        main_artist, collaborators = self._parse_complex_collaboration(clean_name)
        has_collaboration = len(collaborators) > 0
        
        if has_collaboration:
            # For collaborations, we'll handle this in find_recording_mbid
            # by using the artist credit approach
            # Return None here so we can find the full collaboration later
            return None
        
        # Try fuzzy search for full artist name
        if self.use_database:
            result = self.db.fuzzy_search_artist(clean_name)
            if result and isinstance(result, tuple) and len(result) >= 2:
                return result[1]  # Return MBID from tuple (artist_name, mbid, score)
        else:
            # Fallback to API
            try:
                result = self.api.search_artist(clean_name)
                if result:
                    return result['id']
            except:
                pass
        
        return None
    
    def find_recording_mbid(self, artist_mbid: str, title: str) -> Optional[str]:
        clean_title = self._clean_title(title)
        
        # Load known recordings data first
        known_recordings = data_loader.load_known_recordings()
        
        # Check known recordings first
        for (known_artist_mbid, known_title), recording_mbid in known_recordings.items():
            if artist_mbid == known_artist_mbid and clean_title.lower() == known_title.lower():
                return recording_mbid
        
        # Handle collaborations using artist credit
        if self.use_database:
            # If no artist_mbid (collaboration case), try to find by title and original artist name
            if not artist_mbid:
                # This is a collaboration case, try to find by title
                result = self.db.fuzzy_search_recording(clean_title)
                if result and isinstance(result, tuple) and len(result) >= 2:
                    return result[1]  # Return MBID from tuple (recording_name, mbid, score)
            else:
                # Regular case with artist_mbid
                result = self.db.fuzzy_search_recording(clean_title, artist_mbid)
                if result and isinstance(result, tuple) and len(result) >= 2:
                    return result[1]  # Return MBID from tuple (recording_name, mbid, score)
        else:
            # Fallback to API
            try:
                result = self.api.search_recording(clean_title, artist_mbid)
                if result:
                    return result['id']
            except:
                pass
        
        return None
    
    def get_artist_info(self, mbid: str) -> Optional[Dict[str, Any]]:
        if self.use_database:
            return self.db.get_artist_info(mbid)
        else:
            return self.api.get_artist_info(mbid)
    
    def get_recording_info(self, mbid: str) -> Optional[Dict[str, Any]]:
        if self.use_database:
            return self.db.get_recording_info(mbid)
        else:
            return self.api.get_recording_info(mbid)
    
    def _build_artist_string(self, artist_credit: list) -> str:
        if not artist_credit:
            return ""
        parts = []
        for credit in artist_credit:
            parts.append(credit['name'])
            if credit.get('joinphrase'):
                parts.append(credit['joinphrase'])
        return ''.join(parts).strip()

    def _parse_complex_collaboration(self, artist_string: str) -> Tuple[str, List[str]]:
        """
        Parse complex collaboration strings like "Pitbull ft. Ne-Yo, Afrojack & Nayer"
        Returns: (main_artist, [collaborators])
        """
        import re
        
        # Primary collaboration indicators
        primary_patterns = ['ft.', 'feat.', 'featuring', 'ft', 'feat']
        
        # Secondary collaboration indicators (need more careful handling)
        secondary_patterns = ['&', 'and', ',']
        
        # Check if this is a collaboration
        is_collaboration = False
        split_pattern = None
        
        for pattern in primary_patterns:
            if pattern.lower() in artist_string.lower():
                is_collaboration = True
                # Find the actual pattern in the original string (case-insensitive)
                import re
                match = re.search(re.escape(pattern), artist_string, re.IGNORECASE)
                if match:
                    split_pattern = match.group(0)
                else:
                    split_pattern = pattern
                break
        
        # If no primary collaboration found, check secondary patterns
        if not is_collaboration:
            for pattern in secondary_patterns:
                # Use word boundaries to avoid splitting within words like "Orlando"
                pattern_regex = r'\b' + re.escape(pattern) + r'\b'
                if re.search(pattern_regex, artist_string, re.IGNORECASE):
                    # For secondary patterns, be more careful
                    # Check if this looks like a band name vs collaboration
                    parts = re.split(pattern_regex, artist_string, flags=re.IGNORECASE)
                    if len(parts) == 2:
                        part1 = parts[0].strip()
                        part2 = parts[1].strip()
                        
                        # Check if this looks like a band name vs collaboration
                        # Band names typically have simple, short parts
                        # Collaborations often have more complex artist names
                        part1_words = len(part1.split())
                        part2_words = len(part2.split())
                        
                        # Check if this looks like a band name vs collaboration
                        # Band names typically have simple, short parts
                        # Collaborations often have more complex artist names
                        
                        # If one part has a comma, it's likely a collaboration
                        if ',' in part1 or ',' in part2:
                            is_collaboration = True
                            split_pattern = pattern
                            break
                        
                        # For "&" separators, be more permissive
                        # Most "&" separators in music are collaborations, not band names
                        # Only treat very obvious band names as non-collaborations
                        if part1_words == 1 and part2_words == 1:
                            # Check for very obvious band name patterns
                            obvious_band_names = ['simon & garfunkel', 'hall & oates', 'brooks & dunn']
                            if artist_string.lower() in obvious_band_names:
                                # Likely a band name, skip
                                continue
                        
                        # Likely a collaboration
                        is_collaboration = True
                        split_pattern = pattern
                        break
        
        if not is_collaboration:
            return (artist_string, [])
        
        # Split on the pattern
        parts = artist_string.split(split_pattern)
        if len(parts) < 2:
            return (artist_string, [])
        
        main_artist = parts[0].strip()
        collaborators_string = split_pattern.join(parts[1:]).strip()
        
        # Now parse the collaborators string which might have multiple separators
        collaborators = self._parse_collaborators(collaborators_string)
        
        return (main_artist, collaborators)

    def _parse_collaborators(self, collaborators_string: str) -> List[str]:
        """
        Parse a string like "Ne-Yo, Afrojack & Nayer" into individual artists
        """
        import re
        
        if not collaborators_string:
            return []
        
        # Split on common separators: comma, ampersand, "and"
        # Use regex to handle multiple separators
        # Use word boundaries to avoid splitting within words like "Orlando"
        separators = r'[,&]|\b(?:and)\b'
        
        # Split and clean up each part
        parts = re.split(separators, collaborators_string, flags=re.IGNORECASE)
        
        collaborators = []
        for part in parts:
            part = part.strip()
            if part:  # Skip empty parts
                collaborators.append(part)
        
        return collaborators
    
    def clean_song(self, song: Dict[str, Any]) -> Tuple[Dict[str, Any], bool]:
        """
        Clean a single song and return (cleaned_song, success_status)
        """
        original_artist = song.get('artist', '')
        original_title = song.get('title', '')
        
        # Find artist MBID
        artist_mbid = self.find_artist_mbid(song.get('artist', ''))
        
        # Handle collaboration case (artist_mbid is None)
        # Use the same complex collaboration parsing as the database
        artist_name = song.get('artist', '')
        main_artist, collaborators = self._parse_complex_collaboration(artist_name)
        has_collaboration = len(collaborators) > 0
        
        if artist_mbid is None and has_collaboration:
            # Try to find recording using artist credit approach
            if self.use_database:
                result = self.db.find_artist_credit(song.get('artist', ''), song.get('title', ''))
                if result:
                    artist_credit_id, artist_string, recording_mbid = result
                    
                    # Update with the correct artist credit
                    song['artist'] = artist_string
                    song['recording_mbid'] = recording_mbid
                    
                    # For collaborations, try to get the main artist's MBID
                    if self.use_database:
                        main_artist, collaborators = self._parse_complex_collaboration(song.get('artist', ''))
                        if main_artist:
                            artist_result = self.db.fuzzy_search_artist(main_artist)
                            if artist_result and isinstance(artist_result, tuple) and len(artist_result) >= 2:
                                song['mbid'] = artist_result[1]  # Set the main artist's MBID
                    
                    return song, True
                else:
                    return song, False
            else:
                # Fallback to API method
                recording_mbid = self.find_recording_mbid(None, song.get('title', ''))
                if recording_mbid:
                    recording_info = self.get_recording_info(recording_mbid)
                    if recording_info and recording_info.get('artist-credit'):
                        artist_string = self._build_artist_string(recording_info['artist-credit'])
                        if artist_string:
                            song['artist'] = artist_string
                            song['title'] = recording_info['title']
                            song['recording_mbid'] = recording_mbid
                            return song, True
                return song, False
        
        # Regular case (non-collaboration or collaboration not found)
        if not artist_mbid:
            return song, False
        
        # Get artist info
        artist_info = self.get_artist_info(artist_mbid)
        if artist_info:
            song['artist'] = artist_info['name']
            song['mbid'] = artist_mbid
        
        # Find recording MBID
        recording_mbid = self.find_recording_mbid(artist_mbid, song.get('title', ''))
        if not recording_mbid:
            return song, False
        
        # Get recording info
        recording_info = self.get_recording_info(recording_mbid)
        if recording_info:
            # Update artist string if there are multiple artists, but preserve the artist MBID
            if self.use_database and recording_info.get('artist_credit'):
                song['artist'] = recording_info['artist_credit']
                # Keep the original artist MBID even when updating artist name
                if 'mbid' not in song:
                    song['mbid'] = artist_mbid
            elif not self.use_database and recording_info.get('artist-credit'):
                artist_string = self._build_artist_string(recording_info['artist-credit'])
                if artist_string:
                    song['artist'] = artist_string
                    # Keep the original artist MBID even when updating artist name
                    if 'mbid' not in song:
                        song['mbid'] = artist_mbid
            
            song['title'] = recording_info['title']
            song['recording_mbid'] = recording_mbid
            return song, True
        
        return song, False
    
    def process_songs(self, source_file: Path, output_success: Path = None, output_failure: Path = None, limit: Optional[int] = None) -> Dict[str, Any]:
        """
        Process songs from source file and save successful and failed songs to separate files.
        This is the main processing method that handles full dataset processing by default.
        """
        if not source_file.exists():
            print(f'❌ Source file not found: {source_file}')
            return {}
        
        print('🚀 Starting song processing...')
        
        # Load songs
        with open(source_file, 'r') as f:
            all_songs = json.load(f)
        
        if not isinstance(all_songs, list):
            print("Error: Source file should contain a JSON array of songs")
            return {}
        
        # Apply limit if specified
        if limit is not None:
            all_songs = all_songs[:limit]
            print(f"⚠️  Limiting processing to first {limit} songs")
        
        total_songs = len(all_songs)
        print(f'📊 Total songs to process: {total_songs:,}')
        print(f'Using {"database" if self.use_database else "API"} connection')
        print(PROGRESS_SEPARATOR)
        
        # Initialize arrays for batch processing
        successful_songs = []
        failed_songs = []
        
        # Statistics tracking
        stats = {
            'total_processed': 0,
            'artists_found': 0,
            'recordings_found': 0,
            'start_time': time.time()
        }
        
        # Process each song
        for i, song in enumerate(all_songs, 1):
            try:
                result = self.clean_song(song)
                cleaned_song, success = result
                
                artist_found = 'mbid' in cleaned_song
                recording_found = 'recording_mbid' in cleaned_song
                
                # Display progress with counter and status
                artist_name = song.get('artist', 'Unknown')
                title = song.get('title', 'Unknown')
                
                if artist_found and recording_found:
                    stats['artists_found'] += 1
                    stats['recordings_found'] += 1
                    successful_songs.append(cleaned_song)
                    print(f'[{i:,} of {total_songs:,}] ✅ PASS: {artist_name} - {title}')
                else:
                    # Keep the original song in failed_songs array (same format as source)
                    failed_songs.append(song)
                    print(f'[{i:,} of {total_songs:,}] ❌ FAIL: {artist_name} - {title}')
                
                stats['total_processed'] += 1
                
                # Progress update every 100 songs
                if i % 100 == 0:
                    elapsed = time.time() - stats['start_time']
                    rate = i / elapsed if elapsed > 0 else 0
                    success_rate = (stats['artists_found'] / i * 100) if i > 0 else 0
                    print(f'  📈 Progress: {i:,}/{total_songs:,} ({i/total_songs*100:.1f}%) - '
                          f'Success: {success_rate:.1f}% - Rate: {rate:.1f} songs/sec')
                
            except Exception as e:
                print(f'  ❌ Error processing song {i}: {e}')
                # Keep the original song in failed_songs array
                failed_songs.append(song)
                stats['total_processed'] += 1
            
            # Only add delay for API calls, not database queries
            if not self.use_database:
                time.sleep(API_REQUEST_DELAY)
        
        # Determine output file paths
        if output_success is None:
            output_success = source_file.parent / f"{source_file.stem}-success.json"
        if output_failure is None:
            output_failure = source_file.parent / f"{source_file.stem}-failure.json"
        
        # Save successful songs (array format, same as source)
        with open(output_success, 'w', encoding='utf-8') as f:
            json.dump(successful_songs, f, indent=2, ensure_ascii=False)
        
        # Save failed songs (array format, same as source)
        with open(output_failure, 'w', encoding='utf-8') as f:
            json.dump(failed_songs, f, indent=2, ensure_ascii=False)
        
        # Calculate final statistics
        total_time = time.time() - stats['start_time']
        
        # Create human-readable text report
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        report_file = source_file.parent / f"processing_report_{timestamp}.txt"
        
        with open(report_file, 'w', encoding='utf-8') as f:
            f.write("MusicBrainz Data Cleaner - Processing Report\n")
            f.write("=" * 50 + "\n\n")
            f.write(f"Source File: {source_file}\n")
            f.write(f"Processing Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            f.write(f"Processing Time: {total_time:.1f} seconds\n\n")
            
            f.write("SUMMARY\n")
            f.write("-" * 20 + "\n")
            f.write(f"Total Songs Processed: {stats['total_processed']:,}\n")
            f.write(f"Successful Songs: {len(successful_songs):,}\n")
            f.write(f"Failed Songs: {len(failed_songs):,}\n")
            f.write(f"Success Rate: {(len(successful_songs)/stats['total_processed']*100):.1f}%\n\n")
            
            f.write("DETAILED STATISTICS\n")
            f.write("-" * 20 + "\n")
            f.write(f"Artists Found: {stats['artists_found']:,}/{stats['total_processed']:,} ({(stats['artists_found']/stats['total_processed']*100):.1f}%)\n")
            f.write(f"Recordings Found: {stats['recordings_found']:,}/{stats['total_processed']:,} ({(stats['recordings_found']/stats['total_processed']*100):.1f}%)\n")
            f.write(f"Processing Speed: {stats['total_processed'] / total_time:.1f} songs/second\n\n")
            
            f.write("OUTPUT FILES\n")
            f.write("-" * 20 + "\n")
            f.write(f"Successful Songs: {output_success}\n")
            f.write(f"Failed Songs: {output_failure}\n")
            f.write(f"Report File: {report_file}\n\n")
            
            if len(failed_songs) > 0:
                f.write("FAILED SONGS (First 50)\n")
                f.write("-" * 20 + "\n")
                for i, song in enumerate(failed_songs[:50], 1):
                    artist = song.get('artist', 'Unknown')
                    title = song.get('title', 'Unknown')
                    f.write(f"{i:3d}. {artist} - {title}\n")
                
                if len(failed_songs) > 50:
                    f.write(f"... and {len(failed_songs) - 50} more failed songs\n")
        
        # Save detailed JSON report for programmatic access
        json_report_file = source_file.parent / f"processing_report_{timestamp}.json"
        final_stats = {
            'summary': {
                'total_tested': stats['total_processed'],
                'artists_found': stats['artists_found'],
                'recordings_found': stats['recordings_found'],
                'failed_count': len(failed_songs),
                'artist_success_rate': (stats['artists_found'] / stats['total_processed'] * 100) if stats['total_processed'] > 0 else 0,
                'recording_success_rate': (stats['recordings_found'] / stats['total_processed'] * 100) if stats['total_processed'] > 0 else 0,
                'processing_time_seconds': total_time,
                'songs_per_second': stats['total_processed'] / total_time if total_time > 0 else 0
            },
            'files': {
                'source': str(source_file),
                'successful_songs': str(output_success),
                'failed_songs': str(output_failure),
                'text_report': str(report_file),
                'json_report': str(json_report_file)
            }
        }
        
        with open(json_report_file, 'w') as f:
            json.dump(final_stats, f, indent=2)
        
        print(f'\n{PROGRESS_SEPARATOR}')
        print(f'🎉 Processing completed!')
        print(f'📊 Final Results:')
        print(f'  ⏱️  Total processing time: {total_time:.1f} seconds')
        print(f'  🚀 Average speed: {stats["total_processed"] / total_time:.1f} songs/second')
        print(f'  ✅ Artists found: {stats["artists_found"]:,}/{stats["total_processed"]:,} ({stats["artists_found"]/stats["total_processed"]*100:.1f}%)')
        print(f'  ✅ Recordings found: {stats["recordings_found"]:,}/{stats["total_processed"]:,} ({stats["recordings_found"]/stats["total_processed"]*100:.1f}%)')
        print(f'  ❌ Failed songs: {len(failed_songs):,} ({len(failed_songs)/stats["total_processed"]*100:.1f}%)')
        print(f'📄 Files saved:')
        print(f'  ✅ Successful songs: {output_success}')
        print(f'  ❌ Failed songs: {output_failure}')
        print(f'  📋 Text report: {report_file}')
        print(f'  📊 JSON report: {json_report_file}')
        
        return final_stats


def print_help() -> None:
    help_text = """
MusicBrainz Data Cleaner - Clean and normalize song data using MusicBrainz

USAGE:
    musicbrainz-cleaner [options]

OPTIONS:
    --source FILE              Source JSON file (default: data/songs.json)
    --output-success FILE      Output file for successful songs (default: source-success.json)
    --output-failure FILE      Output file for failed songs (default: source-failure.json)
    --limit N                  Process only the first N songs (default: all songs)
    --use-api                  Force use of HTTP API instead of direct database access
    --test-connection          Test connection to MusicBrainz server
    --help, -h                 Show this help message
    --version, -v              Show version information

EXAMPLES:
    # Process all songs with default settings
    musicbrainz-cleaner

    # Process specific file
    musicbrainz-cleaner --source data/my_songs.json

    # Process with custom output files
    musicbrainz-cleaner --source data/songs.json --output-success cleaned.json --output-failure failed.json

    # Process only first 1000 songs
    musicbrainz-cleaner --limit 1000

    # Test connection
    musicbrainz-cleaner --test-connection

    # Force API mode
    musicbrainz-cleaner --use-api

REQUIREMENTS:
    - MusicBrainz server running on http://localhost:5001
    - PostgreSQL database accessible (for direct database access)
    - Python 3.6+ with requests, psycopg2-binary, and fuzzywuzzy libraries

PERFORMANCE:
    - Direct database access is much faster than API calls
    - Fuzzy search provides better matching for similar names
    - No rate limiting with database access
"""
    print(help_text)


def print_version() -> None:
    version_info = """
MusicBrainz Data Cleaner v3.0.0

Enhanced with:
- Direct PostgreSQL database access
- Fuzzy search for better matching
- Improved performance and accuracy
- Separate output files for successful and failed songs
- Detailed progress tracking and reporting

Copyright (c) 2024 MusicBrainz Data Cleaner Contributors
MIT License - see LICENSE file for details

Built with Python 3.6+
"""
    print(version_info)


def parse_arguments(args: List[str]) -> Dict[str, Any]:
    """Parse command line arguments into a dictionary"""
    parsed = {
        'source': 'data/songs.json',
        'output_success': None,
        'output_failure': None,
        'limit': None,
        'use_api': False,
        'test_connection': False,
        'help': False,
        'version': False
    }
    
    i = 0
    while i < len(args):
        arg = args[i]
        
        if arg in ['--help', '-h', 'help']:
            parsed['help'] = True
        elif arg in ['--version', '-v', 'version']:
            parsed['version'] = True
        elif arg == '--test-connection':
            parsed['test_connection'] = True
        elif arg == '--use-api':
            parsed['use_api'] = True
        elif arg == '--source':
            if i + 1 < len(args) and not args[i + 1].startswith('--'):
                parsed['source'] = args[i + 1]
                i += 1
            else:
                print("Error: --source requires a file path")
                sys.exit(ExitCode.USAGE_ERROR)
        elif arg == '--output-success':
            if i + 1 < len(args) and not args[i + 1].startswith('--'):
                parsed['output_success'] = args[i + 1]
                i += 1
            else:
                print("Error: --output-success requires a file path")
                sys.exit(ExitCode.USAGE_ERROR)
        elif arg == '--output-failure':
            if i + 1 < len(args) and not args[i + 1].startswith('--'):
                parsed['output_failure'] = args[i + 1]
                i += 1
            else:
                print("Error: --output-failure requires a file path")
                sys.exit(ExitCode.USAGE_ERROR)
        elif arg == '--limit':
            if i + 1 < len(args) and not args[i + 1].startswith('--'):
                try:
                    parsed['limit'] = int(args[i + 1])
                    if parsed['limit'] <= 0:
                        print("Error: --limit must be a positive number")
                        sys.exit(ExitCode.USAGE_ERROR)
                except ValueError:
                    print("Error: --limit requires a valid number")
                    sys.exit(ExitCode.USAGE_ERROR)
                i += 1
            else:
                print("Error: --limit requires a number")
                sys.exit(ExitCode.USAGE_ERROR)
        
        i += 1
    
    return parsed


def main() -> int:
    try:
        args = sys.argv[1:]
        parsed = parse_arguments(args)
        
        # Handle help and version flags
        if parsed['help']:
            print_help()
            return ExitCode.SUCCESS
        
        if parsed['version']:
            print_version()
            return ExitCode.SUCCESS
        
        # Handle test connection
        if parsed['test_connection']:
            if parsed['use_api']:
                api = MusicBrainzAPIClient()
                if api.test_connection():
                    print("✅ Connection to MusicBrainz API server successful")
                    return ExitCode.SUCCESS
                else:
                    print("❌ Connection to MusicBrainz API server failed")
                    return ExitCode.ERROR
            else:
                db = MusicBrainzDatabase()
                if db.test_connection():
                    print("✅ Connection to MusicBrainz database successful")
                    return ExitCode.SUCCESS
                else:
                    print("❌ Connection to MusicBrainz database failed")
                    return ExitCode.ERROR
        
        # Process songs (main functionality)
        source_file = Path(parsed['source'])
        output_success = Path(parsed['output_success']) if parsed['output_success'] else None
        output_failure = Path(parsed['output_failure']) if parsed['output_failure'] else None
        
        if not source_file.exists():
            print(f"Error: Source file does not exist: {source_file}")
            return ExitCode.USAGE_ERROR
        
        if not source_file.is_file():
            print(f"Error: Source path is not a file: {source_file}")
            return ExitCode.USAGE_ERROR
        
        if source_file.suffix.lower() != '.json':
            print(f"Error: Source file must be a JSON file: {source_file}")
            return ExitCode.USAGE_ERROR
        
        # Process the file
        cleaner = MusicBrainzCleaner(use_database=not parsed['use_api'])
        cleaner.process_songs(source_file, output_success, output_failure, parsed['limit'])
        
        return ExitCode.SUCCESS
        
    except KeyboardInterrupt:
        print("\nOperation cancelled by user")
        return ExitCode.ERROR
    except Exception as e:
        print(f"Unexpected error: {e}")
        return ExitCode.ERROR


if __name__ == "__main__":
    sys.exit(main())
