import os
import sys
from typing import Optional, Any

import typer

from pushtunes.sources.csv import CSVSource
from pushtunes.sources.spotify import SpotifySource
from pushtunes.sources.ytm import YTMSource
from pushtunes.services.spotify import SpotifyService
from pushtunes.services.ytm import YTMService
from pushtunes.services.csv import CSVService
from pushtunes.sources.subsonic import SubsonicSource
from pushtunes.sources.jellyfin import JellyfinSource
from pushtunes.utils.filters import AlbumFilter, TrackFilter
from pushtunes.utils.logging import get_logger, set_console_log_level
from pushtunes.models.compare_status import CompareStatus
from pushtunes.services.album_comparer import AlbumComparer, AlbumCompareResult
from pushtunes.services.track_comparer import TrackComparer, TrackCompareResult
from pushtunes.services.playlist_comparer import PlaylistComparer, PlaylistCompareResult


def create_source(source_type: str, config: dict[str, Any]):
    """Create a music source based on type and configuration."""
    if source_type == "subsonic":
        return SubsonicSource(
            url=config.get("subsonic_url"),
            username=config.get("subsonic_username"),
            password=config.get("subsonic_password"),
            port=config.get("subsonic_port", 443),
        )
    elif source_type == "jellyfin":
        return JellyfinSource(
            url=config.get("jellyfin_url"),
            username=config.get("jellyfin_username"),
            password=config.get("jellyfin_password"),
        )
    elif source_type == "spotify":
        return SpotifySource(
            client_id=config.get("spotify_client_id"),
            client_secret=config.get("spotify_client_secret"),
        )
    elif source_type == "ytm":
        return YTMSource(auth_file=config.get("ytm_auth", "browser.json"))
    elif source_type == "csv":
        return CSVSource(csv_file=config.get("csv_file"))
    else:
        raise ValueError(f"Unsupported source type: {source_type}")


def create_service(service_type: str, config: dict[str, Any]):
    """Create a music service based on type and configuration."""
    if service_type == "spotify":
        return SpotifyService(
            min_similarity=config.get("similarity", 0.8),
            client_id=config.get("spotify_client_id"),
            client_secret=config.get("spotify_client_secret"),
            redirect_uri=config.get("spotify_redirect_uri"),
        )
    elif service_type == "ytm":
        return YTMService(
            min_similarity=config.get("similarity", 0.8),
            auth_file=config.get("ytm_auth_file", "browser.json"),
        )
    elif service_type == "csv":
        csv_file = config.get("csv_file")
        if not csv_file:
            raise ValueError("CSV file path is required when using CSV as a target")
        return CSVService(csv_file=csv_file)
    else:
        raise ValueError(f"Unsupported service type: {service_type}")


def print_stats(stats: dict[str, int], content_type: str = "albums"):
    """Print sync statistics."""
    print("\n" + "=" * 50)
    print("Statistics")
    print("=" * 50)
    print(f"Total {content_type} processed: {stats['total']}")
    if stats.get("skipped_filtered", 0) > 0:
        print(f"Skipped (filtered out): {stats['skipped_filtered']}")
    print(f"Successfully added: {stats['added']}")
    if stats.get("mapped", 0) > 0:
        print(f"Added via mappings: {stats['mapped']}")
    if stats.get("deleted", 0) > 0:
        print(f"Deleted from target: {stats['deleted']}")
    print(f"Skipped (already in library): {stats['skipped_existing']}")
    print(f"Skipped (not found): {stats['skipped_not_found']}")
    print(f"Skipped (low similarity): {stats['skipped_low_similarity']}")
    print(f"Errors: {stats['errors']}")
    print("=" * 50)


from pushtunes.models.push_status import PushStatus
from pushtunes.services.album_pusher import AlbumPusher, AlbumResult


def push_albums(
    source: str = typer.Option(
        ..., "--from", help="Source ('subsonic', 'jellyfin', or 'csv')"
    ),
    target: str = typer.Option(..., "--to", help="Target ('spotify', 'ytm' or 'csv')"),
    similarity: float = typer.Option(
        0.8, help="Minimum similarity threshold for matching (0.0-1.0, default: 0.8)"
    ),
    verbose: bool = typer.Option(
        False, "-v", "--verbose", help="Enable verbose output"
    ),
    log_level: str = typer.Option(
        "INFO",
        "--log-level",
        help="Console log level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)",
    ),
    ytm_auth: str = typer.Option(
        "browser.json",
        help="Path to YouTube Music authentication file (default: browser.json)",
    ),
    filter_patterns: Optional[str] = typer.Option(
        None,
        "--filter",
        help="Filter patterns for albums/artists (e.g., \"artist:'.*dead.*',album:'.*signal.*'\")",
    ),
    filter_file: Optional[str] = typer.Option(
        None, "--filter-from", help="File containing filter patterns (one per line)"
    ),
    csv_file: Optional[str] = typer.Option(
        None, help="Filename of the CSV file to write to or read from"
    ),
    report: Optional[str] = typer.Option(
        None,
        help="Generate detailed report for specific statuses (comma-separated: not_found,filtered,similarity_too_low,already_in_library,added,deleted,error)",
    ),
    color: bool = typer.Option(
        True,
        "--color/--no-color",
        help="Enable/disable colored output (default: enabled)",
    ),
    mappings_file: Optional[str] = typer.Option(
        None,
        "--mappings-file",
        help="CSV file containing mappings for albums that can't be matched automatically",
    ),
    export_csv: Optional[str] = typer.Option(
        None,
        "--export-csv",
        help="Export results with specific statuses to CSV (comma-separated: not_found,filtered,similarity_too_low,already_in_library,already_in_library_cache,added,deleted,error)",
    ),
    export_csv_file: Optional[str] = typer.Option(
        None,
        "--export-csv-file",
        help="Filename for the exported CSV file (default: albums_export_<statuses>.csv)",
    ),
    delete: bool = typer.Option(
        False,
        "--delete",
        help="Delete albums from target that are not present in source (with confirmation and backup)",
    ),
):
    """
    Push albums from a source to a target service.
    """
    # Set console log level
    try:
        set_console_log_level(log_level)
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

    # Create configuration
    config = {
        "similarity": similarity,
        "ytm_auth_file": ytm_auth,
        "csv_file": csv_file,
    }

    log = get_logger()
    try:
        album_filter = None
        if filter_patterns or filter_file:
            try:
                if filter_patterns:
                    album_filter = AlbumFilter.from_string(filter_patterns)
                elif filter_file:
                    album_filter = AlbumFilter.from_file(filter_file)

                if album_filter:
                    log.info(f"Created filter with {len(album_filter)} patterns")
            except (ValueError, FileNotFoundError) as e:
                log.error(f"Error creating filter: {e}")
                sys.exit(1)

        # Create source and target services
        log.info(f"Initializing {source} source...")
        source_obj = create_source(source, config)

        log.info(f"Initializing {target} service...")
        service = create_service(target, config)

        # Get albums from source (invalidate cache first if using --delete)
        if delete:
            log.info("Invalidating source cache for fresh comparison...")
            source_obj.cache.invalidate_album_cache()
        albums = source_obj.get_albums()

        # If target is CSV, write directly to file (no matching needed)
        if target == "csv" and isinstance(service, CSVService):
            log.info(f"Exporting {len(albums)} albums to CSV file {csv_file}...")

            # Apply filter if present
            if album_filter:
                filtered_albums = [a for a in albums if not album_filter.matches(a)]
                log.info(f"After filtering: {len(filtered_albums)} albums")
                albums = filtered_albums

            from pushtunes.utils.csv_utils import export_albums_to_csv

            export_albums_to_csv(albums, csv_file)
            print(f"\nSuccessfully exported {len(albums)} albums to {csv_file}")
            return

        # Perform sync
        log.info(f"Starting albums sync from {source} to {target}...")

        # Load mappings if provided
        mappings = None
        if mappings_file:
            from pushtunes.services.mappings_manager import MappingsManager

            mappings = MappingsManager(mappings_file)

        pusher = AlbumPusher(
            albums=albums,
            service=service,
            filter=album_filter,
            min_similarity=similarity,
            mappings=mappings,
        )
        results: list[AlbumResult] = pusher.push_albums()

        stats = {
            "total": len(results),
            "added": sum(1 for r in results if r.status == PushStatus.added),
            "mapped": sum(1 for r in results if r.status == PushStatus.mapped),
            "skipped_existing": sum(
                1 for r in results if r.status == PushStatus.already_in_library
            ),
            "skipped_not_found": sum(
                1 for r in results if r.status == PushStatus.not_found
            ),
            "skipped_low_similarity": sum(
                1 for r in results if r.status == PushStatus.similarity_too_low
            ),
            "skipped_filtered": sum(
                1 for r in results if r.status == PushStatus.filtered
            ),
            "errors": sum(1 for r in results if r.status == PushStatus.error),
            "deleted": 0,
        }

        print_stats(stats, "albums")

        # Handle deletion if requested
        if delete:
            if target == "csv":
                log.error("--delete option is not supported when target is CSV")
                sys.exit(1)

            log.info("Processing deletion of albums not present in source...")

            from pushtunes.utils.deletion_manager import DeletionManager
            from pushtunes.utils.deletion_confirm import (
                display_deletion_preview,
                confirm_deletion,
            )

            # Initialize deletion manager
            deletion_manager = DeletionManager()

            # Get target library from cache (already fresh from the push operation)
            log.info("Getting target library from cache...")
            target_albums = service.cache.albums
            log.info(f"Found {len(target_albums)} albums in target library")

            # Generate deletion preview
            log.info("Analyzing which albums would be deleted...")
            preview = deletion_manager.generate_deletion_preview(
                target_items=target_albums,
                source_items=albums,
                min_similarity=similarity,
            )

            # Display preview
            display_deletion_preview(preview, "albums", similarity, color)

            # If there are items to delete, backup and confirm
            if preview.items_to_delete:
                # Backup albums that will be deleted
                albums_to_delete = [c.item for c in preview.items_to_delete]
                backup_file = deletion_manager.backup_albums(
                    albums_to_delete, "deletion"
                )

                # Ask for confirmation
                if confirm_deletion(preview, "albums", backup_file, color):
                    # Perform deletion
                    deletion_results = []
                    for candidate in preview.items_to_delete:
                        album = candidate.item
                        success = service.remove_album(album)
                        if success:
                            deletion_results.append(
                                AlbumResult(album=album, status=PushStatus.deleted)
                            )
                            log.info(f"Deleted: {album.artist} - {album.title}")
                        else:
                            deletion_results.append(
                                AlbumResult(
                                    album=album,
                                    status=PushStatus.error,
                                    message="Failed to delete",
                                )
                            )
                            log.error(
                                f"Failed to delete: {album.artist} - {album.title}"
                            )

                    # Update stats
                    stats["deleted"] = sum(
                        1 for r in deletion_results if r.status == PushStatus.deleted
                    )
                    stats["errors"] += sum(
                        1 for r in deletion_results if r.status == PushStatus.error
                    )

                    # Add deletion results to main results
                    results.extend(deletion_results)

                    print(f"\nDeleted {stats['deleted']} albums from target library")
                    if stats["deleted"] > 0:
                        print(f"Backup saved to: {backup_file}")
                else:
                    print("\nDeletion cancelled. No albums were deleted.")
            else:
                print("\nNo albums to delete. Target library is in sync with source.")

        # Generate detailed report if requested
        if report:
            from pushtunes.utils.reporting import generate_report

            report_statuses = [s.strip() for s in report.split(",")]
            generate_report(
                results, report_statuses, result_type="album", use_color=color
            )

        # Export results to CSV if requested
        if export_csv:
            export_statuses = [s.strip() for s in export_csv.split(",")]

            # Check if user wants mappings file format
            if "mappings-file" in export_statuses:
                from pushtunes.utils.csv_utils import (
                    export_album_results_to_mappings_csv,
                )

                # Generate default filename if not provided
                if not export_csv_file:
                    export_filename = "albums_mappings_template.csv"
                else:
                    export_filename = os.path.expanduser(export_csv_file)

                # Export not_found and similarity_too_low items in mappings format
                new_count, unmapped_count = export_album_results_to_mappings_csv(
                    results, ["not_found", "similarity_too_low"], export_filename
                )

                if new_count > 0 or unmapped_count > 0:
                    if new_count > 0:
                        print(
                            f"\nExported {new_count} NEW albums to mappings template: {export_filename}"
                        )
                    if unmapped_count > 0:
                        print(
                            f"  {unmapped_count} failed albums already in {export_filename} with empty target fields"
                        )
                        print(
                            f"  These items are still failing - fill in their target fields to fix them"
                        )
                    if new_count > 0:
                        print(
                            f"  Fill in the target fields and use with --mappings-file={export_filename}"
                        )
                else:
                    print(
                        f"\nNo albums with status 'not_found' or 'similarity_too_low' to export"
                    )
            else:
                from pushtunes.utils.csv_utils import export_album_results_to_csv

                # Generate default filename if not provided
                if not export_csv_file:
                    statuses_str = "_".join(export_statuses)
                    export_filename = f"albums_export_{statuses_str}.csv"
                else:
                    export_filename = os.path.expanduser(export_csv_file)

                exported_count = export_album_results_to_csv(
                    results, export_statuses, export_filename
                )
                if exported_count > 0:
                    print(f"\nExported {exported_count} albums to {export_filename}")
                else:
                    print(f"\nNo albums matched the specified statuses for export")

        # Exit with appropriate code
        if stats["errors"] > 0:
            print("\nWarning: Some errors occurred during sync")
            sys.exit(1)
        else:
            print("\nSync completed successfully")

    except KeyboardInterrupt:
        print("\nSync interrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        if verbose:
            import traceback

            traceback.print_exc()
        sys.exit(1)


from pushtunes.services.track_pusher import TrackPusher, TrackResult
from pushtunes.services.playlist_pusher import (
    PlaylistPusher,
    PlaylistResult,
    ConflictMode,
    pretty_print_track_result,
)


def push_tracks(
    source: str = typer.Option(
        ..., "--from", help="Source ('subsonic', 'jellyfin', 'spotify', 'ytm' or 'csv')"
    ),
    target: str = typer.Option(..., "--to", help="Target ('spotify', 'ytm' or 'csv')"),
    similarity: float = typer.Option(
        0.8, help="Minimum similarity threshold for matching (0.0-1.0, default: 0.8)"
    ),
    verbose: bool = typer.Option(
        False, "-v", "--verbose", help="Enable verbose output"
    ),
    log_level: str = typer.Option(
        "INFO",
        "--log-level",
        help="Console log level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)",
    ),
    filter_patterns: Optional[str] = typer.Option(
        None,
        "--filter",
        help="Filter patterns for tracks/artists/albums (e.g., \"artist:'.*dead.*',track:'.*signal.*'\")",
    ),
    filter_file: Optional[str] = typer.Option(
        None, "--filter-from", help="File containing filter patterns (one per line)"
    ),
    csv_file: Optional[str] = typer.Option(
        None, help="Filename of the CSV file to write to or read from"
    ),
    report: Optional[str] = typer.Option(
        None,
        help="Generate detailed report for specific statuses (comma-separated: not_found,filtered,similarity_too_low,already_in_library,added,deleted,error)",
    ),
    color: bool = typer.Option(
        True,
        "--color/--no-color",
        help="Enable/disable colored output (default: enabled)",
    ),
    mappings_file: Optional[str] = typer.Option(
        None,
        "--mappings-file",
        help="CSV file containing mappings for tracks that can't be matched automatically",
    ),
    export_csv: Optional[str] = typer.Option(
        None,
        "--export-csv",
        help="Export results with specific statuses to CSV (comma-separated: not_found,filtered,similarity_too_low,already_in_library,already_in_library_cache,added,deleted,error)",
    ),
    export_csv_file: Optional[str] = typer.Option(
        None,
        "--export-csv-file",
        help="Filename for the exported CSV file (default: tracks_export_<statuses>.csv)",
    ),
    delete: bool = typer.Option(
        False,
        "--delete",
        help="Delete tracks from target that are not present in source (with confirmation and backup)",
    ),
):
    """
    Push tracks from a source to a target service.
    """
    # Set console log level
    try:
        set_console_log_level(log_level)
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

    # Create configuration
    config = {
        "similarity": similarity,
        "csv_file": csv_file,
    }

    log = get_logger()
    try:
        track_filter = None
        if filter_patterns or filter_file:
            try:
                if filter_patterns:
                    track_filter = TrackFilter.from_string(filter_patterns)
                elif filter_file:
                    track_filter = TrackFilter.from_file(filter_file)

                if track_filter:
                    log.info(f"Created filter with {len(track_filter)} patterns")
            except (ValueError, FileNotFoundError) as e:
                log.error(f"Error creating filter: {e}")
                sys.exit(1)

        # Create source and target services
        log.info(f"Initializing {source} source...")
        source_obj = create_source(source, config)

        log.info(f"Initializing {target} service...")
        service = create_service(target, config)

        # Get tracks from source (invalidate cache first if using --delete)
        if delete:
            log.info("Invalidating source cache for fresh comparison...")
            source_obj.cache.invalidate_track_cache()
        tracks = source_obj.get_tracks()

        # If target is CSV, write directly to file (no matching needed)
        if target == "csv" and isinstance(service, CSVService):
            log.info(f"Exporting {len(tracks)} tracks to CSV file {csv_file}...")

            # Apply filter if present
            if track_filter:
                filtered_tracks = [t for t in tracks if not track_filter.matches(t)]
                log.info(f"After filtering: {len(filtered_tracks)} tracks")
                tracks = filtered_tracks

            from pushtunes.utils.csv_utils import export_tracks_to_csv

            export_tracks_to_csv(tracks, csv_file)
            print(f"\nSuccessfully exported {len(tracks)} tracks to {csv_file}")
            return

        # Perform sync
        log.info(f"Starting tracks sync from {source} to {target}...")

        # Load mappings if provided
        mappings = None
        if mappings_file:
            from pushtunes.services.mappings_manager import MappingsManager

            mappings = MappingsManager(mappings_file)

        pusher = TrackPusher(
            tracks=tracks,
            service=service,
            filter=track_filter,
            min_similarity=similarity,
            mappings=mappings,
        )
        results: list[TrackResult] = pusher.push_tracks()

        stats = {
            "total": len(results),
            "added": sum(1 for r in results if r.status == PushStatus.added),
            "mapped": sum(1 for r in results if r.status == PushStatus.mapped),
            "skipped_existing": sum(
                1 for r in results if r.status == PushStatus.already_in_library
            ),
            "skipped_not_found": sum(
                1 for r in results if r.status == PushStatus.not_found
            ),
            "skipped_low_similarity": sum(
                1 for r in results if r.status == PushStatus.similarity_too_low
            ),
            "skipped_filtered": sum(
                1 for r in results if r.status == PushStatus.filtered
            ),
            "errors": sum(1 for r in results if r.status == PushStatus.error),
            "deleted": 0,
        }

        print_stats(stats, "tracks")

        # Handle deletion if requested
        if delete:
            if target == "csv":
                log.error("--delete option is not supported when target is CSV")
                sys.exit(1)

            log.info("Processing deletion of tracks not present in source...")

            from pushtunes.utils.deletion_manager import DeletionManager
            from pushtunes.utils.deletion_confirm import (
                display_deletion_preview,
                confirm_deletion,
            )

            # Initialize deletion manager
            deletion_manager = DeletionManager()

            # Get target library from cache (already fresh from the push operation)
            log.info("Getting target library from cache...")
            target_tracks = service.cache.tracks
            log.info(f"Found {len(target_tracks)} tracks in target library")

            # Generate deletion preview
            log.info("Analyzing which tracks would be deleted...")
            preview = deletion_manager.generate_deletion_preview(
                target_items=target_tracks,
                source_items=tracks,
                min_similarity=similarity,
            )

            # Display preview
            display_deletion_preview(preview, "tracks", similarity, color)

            # If there are items to delete, backup and confirm
            if preview.items_to_delete:
                # Backup tracks that will be deleted
                tracks_to_delete = [c.item for c in preview.items_to_delete]
                backup_file = deletion_manager.backup_tracks(
                    tracks_to_delete, "deletion"
                )

                # Ask for confirmation
                if confirm_deletion(preview, "tracks", backup_file, color):
                    # Perform deletion
                    deletion_results = []
                    for candidate in preview.items_to_delete:
                        track = candidate.item
                        success = service.remove_track(track)
                        if success:
                            deletion_results.append(
                                TrackResult(track=track, status=PushStatus.deleted)
                            )
                            log.info(f"Deleted: {track.artist} - {track.title}")
                        else:
                            deletion_results.append(
                                TrackResult(
                                    track=track,
                                    status=PushStatus.error,
                                    message="Failed to delete",
                                )
                            )
                            log.error(
                                f"Failed to delete: {track.artist} - {track.title}"
                            )

                    # Update stats
                    stats["deleted"] = sum(
                        1 for r in deletion_results if r.status == PushStatus.deleted
                    )
                    stats["errors"] += sum(
                        1 for r in deletion_results if r.status == PushStatus.error
                    )

                    # Add deletion results to main results
                    results.extend(deletion_results)

                    print(f"\nDeleted {stats['deleted']} tracks from target library")
                    if stats["deleted"] > 0:
                        print(f"Backup saved to: {backup_file}")
                else:
                    print("\nDeletion cancelled. No tracks were deleted.")
            else:
                print("\nNo tracks to delete. Target library is in sync with source.")

        # Generate detailed report if requested
        if report:
            from pushtunes.utils.reporting import generate_report

            report_statuses = [s.strip() for s in report.split(",")]
            generate_report(
                results, report_statuses, result_type="track", use_color=color
            )

        # Export results to CSV if requested
        if export_csv:
            export_statuses = [s.strip() for s in export_csv.split(",")]

            # Check if user wants mappings file format
            if "mappings-file" in export_statuses:
                from pushtunes.utils.csv_utils import (
                    export_track_results_to_mappings_csv,
                )

                # Generate default filename if not provided
                if not export_csv_file:
                    export_filename = "tracks_mappings_template.csv"
                else:
                    export_filename = os.path.expanduser(export_csv_file)

                # Export not_found and similarity_too_low items in mappings format
                new_count, unmapped_count = export_track_results_to_mappings_csv(
                    results, ["not_found", "similarity_too_low"], export_filename
                )

                if new_count > 0 or unmapped_count > 0:
                    if new_count > 0:
                        print(
                            f"\nExported {new_count} NEW tracks to mappings template: {export_filename}"
                        )
                        print(
                            f"  Fill in the target fields and use with --mappings-file={export_filename}"
                        )
                    if unmapped_count > 0:
                        print(
                            f"  {unmapped_count} failed tracks already in {export_filename} with empty target fields"
                        )
                        print(
                            f"  These items are still failing - fill in their target fields to fix them"
                        )
                else:
                    print(
                        f"\nNo tracks with status 'not_found' or 'similarity_too_low' to export"
                    )
            else:
                from pushtunes.utils.csv_utils import export_track_results_to_csv

                # Generate default filename if not provided
                if not export_csv_file:
                    statuses_str = "_".join(export_statuses)
                    export_filename = f"tracks_export_{statuses_str}.csv"
                else:
                    export_filename = os.path.expanduser(export_csv_file)

                exported_count = export_track_results_to_csv(
                    results, export_statuses, export_filename
                )
                if exported_count > 0:
                    print(f"\nExported {exported_count} tracks to {export_filename}")
                else:
                    print(f"\nNo tracks matched the specified statuses for export")

        # Exit with appropriate code
        if stats["errors"] > 0:
            print("\nWarning: Some errors occurred during sync")
            sys.exit(1)
        else:
            print("\nSync completed successfully")

    except KeyboardInterrupt:
        print("\nSync interrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        if verbose:
            import traceback

            traceback.print_exc()
        sys.exit(1)


def push_playlist(
    source: str = typer.Option(
        ...,
        "--from",
        help="Source ('subsonic', 'jellyfin', 'spotify', 'ytm', or 'csv')",
    ),
    target: str = typer.Option(..., "--to", help="Target ('spotify', 'ytm', or 'csv')"),
    playlist_name: Optional[str] = typer.Option(
        None, "--playlist-name", help="Name of the playlist to push (required)"
    ),
    source_playlist_id: Optional[str] = typer.Option(
        None,
        "--source-playlist-id",
        help="ID of source playlist (Spotify/YTM only, for direct lookup)",
    ),
    playlist_id: Optional[str] = typer.Option(
        None,
        "--playlist-id",
        help="ID of existing playlist on target (Spotify only, for conflict resolution)",
    ),
    similarity: float = typer.Option(
        0.8,
        help="Minimum similarity threshold for matching tracks (0.0-1.0, default: 0.8)",
    ),
    require_all_tracks: bool = typer.Option(
        False,
        "--require-all-tracks",
        help="Require all tracks to match (fail if any track can't be matched)",
    ),
    verbose: bool = typer.Option(
        False, "-v", "--verbose", help="Enable verbose output"
    ),
    log_level: str = typer.Option(
        "INFO",
        "--log-level",
        help="Console log level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)",
    ),
    ytm_auth: str = typer.Option(
        "browser.json", help="Path to YouTube Music authentication file"
    ),
    csv_file: Optional[str] = typer.Option(
        None, help="Filename of the CSV file to write to or read from"
    ),
    report: Optional[str] = typer.Option(
        None,
        help="Generate detailed report for specific statuses (comma-separated: not_found,matched,similarity_too_low)",
    ),
    color: bool = typer.Option(
        True,
        "--color/--no-color",
        help="Enable/disable colored output (default: enabled)",
    ),
    on_conflict: str = typer.Option(
        "abort",
        "--on-conflict",
        help="How to handle conflicts: 'abort' (show differences), 'replace' (replace entire playlist), 'append' (add missing tracks), 'sync' (add missing, remove extras)",
    ),
    mappings_file: Optional[str] = typer.Option(
        None,
        "--mappings-file",
        help="CSV file containing mappings for tracks that can't be matched automatically",
    ),
):
    """
    Push a playlist from a source to a target service, preserving track order.
    """
    # Set console log level
    try:
        set_console_log_level(log_level)
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

    # Create configuration
    config = {
        "similarity": similarity,
        "ytm_auth_file": ytm_auth,
        "csv_file": csv_file,
    }

    log = get_logger()
    try:
        # Validate inputs
        if not playlist_name:
            print("Error: --playlist-name is required")
            sys.exit(1)

        # Parse conflict mode
        try:
            conflict_mode = ConflictMode[on_conflict]
        except KeyError:
            print(
                f"Error: Invalid conflict mode '{on_conflict}'. Valid options: abort, replace, append, sync"
            )
            sys.exit(1)

        # Create source and target services
        log.info(f"Initializing {source} source...")
        source_obj = create_source(source, config)

        log.info(f"Initializing {target} service...")
        service = create_service(target, config)

        # Get playlist from source
        log.info(f"Fetching playlist '{playlist_name}' from {source}...")
        playlist = source_obj.get_playlist(
            playlist_name, playlist_id=source_playlist_id
        )

        if not playlist:
            print(f"Error: Playlist '{playlist_name}' not found on {source}")
            sys.exit(1)

        # If target is CSV, write directly to file (no matching needed)
        if target == "csv" and isinstance(service, CSVService):
            log.info(
                f"Exporting playlist '{playlist.name}' with {len(playlist.tracks)} tracks to CSV file {csv_file}..."
            )
            from pushtunes.utils.csv_utils import export_playlist_to_csv

            export_playlist_to_csv(playlist, csv_file)
            print(
                f"\nSuccessfully exported playlist '{playlist.name}' with {len(playlist.tracks)} tracks to {csv_file}"
            )
            return

        # Push playlist to target
        log.info(f"Pushing playlist '{playlist.name}' to {target}...")

        # Load mappings if provided
        mappings = None
        if mappings_file:
            from pushtunes.services.mappings_manager import MappingsManager

            mappings = MappingsManager(mappings_file)

        pusher = PlaylistPusher(
            playlist=playlist,
            service=service,
            min_similarity=similarity,
            conflict_mode=conflict_mode,
            target_playlist_id=playlist_id,  # Pass the specific ID if provided
            mappings=mappings,
            require_all_tracks=require_all_tracks,
        )
        result: PlaylistResult = pusher.push_playlist()

        # Print detailed results
        print(f"\n{'=' * 60}")
        print(f"Playlist: {playlist.name}")
        print(f"{'=' * 60}")

        # Count statuses
        stats = {
            "total": len(result.track_results),
            "matched": sum(
                1 for r in result.track_results if r.status == PushStatus.matched
            ),
            "not_found": sum(
                1 for r in result.track_results if r.status == PushStatus.not_found
            ),
            "similarity_too_low": sum(
                1
                for r in result.track_results
                if r.status == PushStatus.similarity_too_low
            ),
        }

        print(f"\nTrack Matching Results:")
        print(f"  Total tracks:          {stats['total']}")
        print(f"  Successfully matched:  {stats['matched']}")
        print(f"  Not found:             {stats['not_found']}")
        print(f"  Similarity too low:    {stats['similarity_too_low']}")

        # Show failed matches if verbose
        if verbose:
            print("\nDetailed track results:")
            for track_result in result.track_results:
                print(f"  {pretty_print_track_result(track_result)}")

        # Show conflict information if present
        if result.conflict:
            print(f"\nPlaylist Conflict:")
            print(f"  Existing tracks:   {result.conflict.existing_track_count}")
            print(f"  Source tracks:     {result.conflict.source_track_count}")
            print(f"  Tracks in common:  {len(result.conflict.tracks_in_common)}")
            print(f"  Tracks to add:     {len(result.conflict.tracks_to_add)}")
            print(f"  Tracks to remove:  {len(result.conflict.tracks_to_remove)}")

            if verbose and result.conflict.tracks_to_add:
                print("\nTracks to add:")
                for track in result.conflict.tracks_to_add:
                    print(f"  + {track.artist} - {track.title}")

            if verbose and result.conflict.tracks_to_remove:
                print("\nTracks to remove:")
                for track in result.conflict.tracks_to_remove:
                    print(f"  - {track.artist} - {track.title}")

        # Generate detailed report if requested
        if report:
            from pushtunes.utils.reporting import generate_report

            report_statuses = [s.strip() for s in report.split(",")]
            generate_report(
                result.track_results,
                report_statuses,
                result_type="playlist",
                use_color=color,
            )

        # Show final status
        print(f"\n{'=' * 60}")
        if result.success:
            if result.conflict:
                print(f"{result.message}")
            else:
                print(f"Successfully created playlist on {target}")
            print(f"  Playlist ID: {result.playlist_id}")
            print(f"  Tracks: {stats['matched']}/{stats['total']}")
        else:
            print(f"Error: {result.message}")
            if result.conflict and conflict_mode == ConflictMode.abort:
                print(f"\nTo resolve this conflict, use one of:")
                print(f"  --on-conflict=replace  # Replace entire playlist")
                print(f"  --on-conflict=append    # Add missing tracks only")
                print(f"  --on-conflict=sync     # Add missing, remove extras")
            sys.exit(1)

    except KeyboardInterrupt:
        print("\nPlaylist push interrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        if verbose:
            import traceback

            traceback.print_exc()
        sys.exit(1)


# ============================================================================
# Compare Commands
# ============================================================================


def create_source_or_service(source_type: str, config: dict[str, Any]):
    """Create a music source or service based on type and configuration.

    This is used for compare commands where both --from and --to can be
    either sources (subsonic, jellyfin, csv) or services (spotify, ytm).
    """
    # Try to create as a source first
    if source_type in ["subsonic", "jellyfin", "csv", "spotify", "ytm"]:
        if source_type in ["subsonic", "jellyfin", "csv"]:
            return create_source(source_type, config)
        elif source_type in ["spotify", "ytm"]:
            # These can be both sources and services
            # For compare, we want to get their library, so create as sources
            return create_source(source_type, config)

    raise ValueError(f"Unsupported source/service type: {source_type}")


def print_compare_stats(stats: dict[str, int], content_type: str = "albums"):
    """Print comparison statistics."""
    print("\n" + "=" * 50)
    print("Comparison Results")
    print("=" * 50)
    print(f"Total {content_type} compared: {stats['total']}")
    print(f"In both: {stats['in_both']}")
    print(f"Only in source: {stats['only_in_source']}")
    print(f"Only in target: {stats['only_in_target']}")
    if stats.get("filtered", 0) > 0:
        print(f"Filtered out: {stats['filtered']}")
    if stats.get("errors", 0) > 0:
        print(f"Errors: {stats['errors']}")
    print("=" * 50)


def compare_albums(
    source: str = typer.Option(
        ...,
        "--from",
        help="Source ('subsonic', 'jellyfin', 'csv', 'spotify', or 'ytm')",
    ),
    target: str = typer.Option(
        ..., "--to", help="Target ('subsonic', 'jellyfin', 'csv', 'spotify', or 'ytm')"
    ),
    similarity: float = typer.Option(
        0.8, help="Minimum similarity threshold for matching (0.0-1.0, default: 0.8)"
    ),
    verbose: bool = typer.Option(
        False, "-v", "--verbose", help="Enable verbose output"
    ),
    log_level: str = typer.Option(
        "INFO",
        "--log-level",
        help="Console log level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)",
    ),
    ytm_auth: str = typer.Option(
        "browser.json",
        help="Path to YouTube Music authentication file (default: browser.json)",
    ),
    filter_patterns: Optional[str] = typer.Option(
        None,
        "--filter",
        help="Filter patterns for albums/artists (e.g., \"artist:'.*dead.*',album:'.*signal.*'\")",
    ),
    filter_file: Optional[str] = typer.Option(
        None, "--filter-from", help="File containing filter patterns (one per line)"
    ),
    csv_file: Optional[str] = typer.Option(
        None, help="Filename of the CSV file to read from (for CSV source/target)"
    ),
    mappings_file: Optional[str] = typer.Option(
        None,
        "--mappings-file",
        help="CSV file containing mappings for albums that can't be matched automatically",
    ),
    color: bool = typer.Option(
        True,
        "--color/--no-color",
        help="Enable/disable colored output (default: enabled)",
    ),
):
    """
    Compare albums between two sources/services.
    """
    # Set console log level
    try:
        set_console_log_level(log_level)
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

    # Create configuration
    config = {
        "similarity": similarity,
        "ytm_auth_file": ytm_auth,
        "csv_file": csv_file,
    }

    log = get_logger()
    try:
        album_filter = None
        if filter_patterns or filter_file:
            try:
                if filter_patterns:
                    album_filter = AlbumFilter.from_string(filter_patterns)
                elif filter_file:
                    album_filter = AlbumFilter.from_file(filter_file)

                if album_filter:
                    log.info(f"Created filter with {len(album_filter)} patterns")
            except (ValueError, FileNotFoundError) as e:
                log.error(f"Error creating filter: {e}")
                sys.exit(1)

        # Create source and target
        log.info(f"Initializing {source} source...")
        source_obj = create_source_or_service(source, config)

        log.info(f"Initializing {target} target...")
        target_obj = create_source_or_service(target, config)

        # Get albums from both source and target
        log.info(f"Fetching albums from {source}...")
        albums_source = source_obj.get_albums()
        log.info(f"Fetched {len(albums_source)} albums from {source}")

        log.info(f"Fetching albums from {target}...")
        albums_target = target_obj.get_albums()
        log.info(f"Fetched {len(albums_target)} albums from {target}")

        # Load mappings if provided
        mappings = None
        if mappings_file:
            from pushtunes.services.mappings_manager import MappingsManager

            mappings = MappingsManager(mappings_file)

        # Perform comparison
        log.info(f"Starting album comparison between {source} and {target}...")
        comparer = AlbumComparer(
            albums_source=albums_source,
            albums_target=albums_target,
            filter=album_filter,
            min_similarity=similarity,
            mappings=mappings,
        )
        results: list[AlbumCompareResult] = comparer.compare_albums()

        # Calculate statistics
        stats = {
            "total": len(results),
            "in_both": sum(1 for r in results if r.status == CompareStatus.in_both),
            "only_in_source": sum(
                1 for r in results if r.status == CompareStatus.only_in_source
            ),
            "only_in_target": sum(
                1 for r in results if r.status == CompareStatus.only_in_target
            ),
            "filtered": sum(1 for r in results if r.status == CompareStatus.filtered),
            "errors": sum(1 for r in results if r.status == CompareStatus.error),
        }

        print_compare_stats(stats, "albums")

        # Print detailed results grouped by status
        print("\n" + "=" * 50)
        print("Detailed Results")
        print("=" * 50)

        # Only in source
        only_in_source = [
            r for r in results if r.status == CompareStatus.only_in_source
        ]
        if only_in_source:
            print(f"\nAlbums only in {source} ({len(only_in_source)}):")
            for result in only_in_source:
                print(f"  - {result.album.artist} - {result.album.title}")

        # Only in target
        only_in_target = [
            r for r in results if r.status == CompareStatus.only_in_target
        ]
        if only_in_target:
            print(f"\nAlbums only in {target} ({len(only_in_target)}):")
            for result in only_in_target:
                print(f"  - {result.album.artist} - {result.album.title}")

        # In both (optionally show if verbose)
        if verbose:
            in_both = [r for r in results if r.status == CompareStatus.in_both]
            if in_both:
                print(f"\nAlbums in both ({len(in_both)}):")
                for result in in_both:
                    print(f"  - {result.album.artist} - {result.album.title}")

        print("\nComparison completed successfully")

    except KeyboardInterrupt:
        print("\nComparison interrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        if verbose:
            import traceback

            traceback.print_exc()
        sys.exit(1)


def compare_tracks(
    source: str = typer.Option(
        ...,
        "--from",
        help="Source ('subsonic', 'jellyfin', 'csv', 'spotify', or 'ytm')",
    ),
    target: str = typer.Option(
        ..., "--to", help="Target ('subsonic', 'jellyfin', 'csv', 'spotify', or 'ytm')"
    ),
    similarity: float = typer.Option(
        0.8, help="Minimum similarity threshold for matching (0.0-1.0, default: 0.8)"
    ),
    verbose: bool = typer.Option(
        False, "-v", "--verbose", help="Enable verbose output"
    ),
    log_level: str = typer.Option(
        "INFO",
        "--log-level",
        help="Console log level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)",
    ),
    ytm_auth: str = typer.Option(
        "browser.json",
        help="Path to YouTube Music authentication file (default: browser.json)",
    ),
    filter_patterns: Optional[str] = typer.Option(
        None,
        "--filter",
        help="Filter patterns for tracks/artists (e.g., \"artist:'.*dead.*',track:'.*signal.*'\")",
    ),
    filter_file: Optional[str] = typer.Option(
        None, "--filter-from", help="File containing filter patterns (one per line)"
    ),
    csv_file: Optional[str] = typer.Option(
        None, help="Filename of the CSV file to read from (for CSV source/target)"
    ),
    mappings_file: Optional[str] = typer.Option(
        None,
        "--mappings-file",
        help="CSV file containing mappings for tracks that can't be matched automatically",
    ),
    color: bool = typer.Option(
        True,
        "--color/--no-color",
        help="Enable/disable colored output (default: enabled)",
    ),
):
    """
    Compare tracks between two sources/services.
    """
    # Set console log level
    try:
        set_console_log_level(log_level)
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

    # Create configuration
    config = {
        "similarity": similarity,
        "ytm_auth_file": ytm_auth,
        "csv_file": csv_file,
    }

    log = get_logger()
    try:
        track_filter = None
        if filter_patterns or filter_file:
            try:
                if filter_patterns:
                    track_filter = TrackFilter.from_string(filter_patterns)
                elif filter_file:
                    track_filter = TrackFilter.from_file(filter_file)

                if track_filter:
                    log.info(f"Created filter with {len(track_filter)} patterns")
            except (ValueError, FileNotFoundError) as e:
                log.error(f"Error creating filter: {e}")
                sys.exit(1)

        # Create source and target
        log.info(f"Initializing {source} source...")
        source_obj = create_source_or_service(source, config)

        log.info(f"Initializing {target} target...")
        target_obj = create_source_or_service(target, config)

        # Get tracks from both source and target
        log.info(f"Fetching tracks from {source}...")
        tracks_source = source_obj.get_tracks()
        log.info(f"Fetched {len(tracks_source)} tracks from {source}")

        log.info(f"Fetching tracks from {target}...")
        tracks_target = target_obj.get_tracks()
        log.info(f"Fetched {len(tracks_target)} tracks from {target}")

        # Load mappings if provided
        mappings = None
        if mappings_file:
            from pushtunes.services.mappings_manager import MappingsManager

            mappings = MappingsManager(mappings_file)

        # Perform comparison
        log.info(f"Starting track comparison between {source} and {target}...")
        comparer = TrackComparer(
            tracks_source=tracks_source,
            tracks_target=tracks_target,
            filter=track_filter,
            min_similarity=similarity,
            mappings=mappings,
        )
        results: list[TrackCompareResult] = comparer.compare_tracks()

        # Calculate statistics
        stats = {
            "total": len(results),
            "in_both": sum(1 for r in results if r.status == CompareStatus.in_both),
            "only_in_source": sum(
                1 for r in results if r.status == CompareStatus.only_in_source
            ),
            "only_in_target": sum(
                1 for r in results if r.status == CompareStatus.only_in_target
            ),
            "filtered": sum(1 for r in results if r.status == CompareStatus.filtered),
            "errors": sum(1 for r in results if r.status == CompareStatus.error),
        }

        print_compare_stats(stats, "tracks")

        # Print detailed results grouped by status
        print("\n" + "=" * 50)
        print("Detailed Results")
        print("=" * 50)

        # Only in source
        only_in_source = [
            r for r in results if r.status == CompareStatus.only_in_source
        ]
        if only_in_source:
            print(f"\nTracks only in {source} ({len(only_in_source)}):")
            for result in only_in_source:
                print(f"  - {result.track.artist} - {result.track.title}")

        # Only in target
        only_in_target = [
            r for r in results if r.status == CompareStatus.only_in_target
        ]
        if only_in_target:
            print(f"\nTracks only in {target} ({len(only_in_target)}):")
            for result in only_in_target:
                print(f"  - {result.track.artist} - {result.track.title}")

        # In both (optionally show if verbose)
        if verbose:
            in_both = [r for r in results if r.status == CompareStatus.in_both]
            if in_both:
                print(f"\nTracks in both ({len(in_both)}):")
                for result in in_both:
                    print(f"  - {result.track.artist} - {result.track.title}")

        print("\nComparison completed successfully")

    except KeyboardInterrupt:
        print("\nComparison interrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        if verbose:
            import traceback

            traceback.print_exc()
        sys.exit(1)


def compare_playlist(
    source: str = typer.Option(
        ..., "--from", help="Source ('subsonic', 'jellyfin', 'spotify', or 'ytm')"
    ),
    target: str = typer.Option(
        ..., "--to", help="Target ('subsonic', 'jellyfin', 'spotify', or 'ytm')"
    ),
    playlist_name_source: str = typer.Option(
        ..., "--playlist-name", help="Name of the playlist in the source"
    ),
    playlist_name_target: Optional[str] = typer.Option(
        None,
        "--playlist-name-target",
        help="Name of the playlist in the target (defaults to same as source)",
    ),
    similarity: float = typer.Option(
        0.8, help="Minimum similarity threshold for matching (0.0-1.0, default: 0.8)"
    ),
    verbose: bool = typer.Option(
        False, "-v", "--verbose", help="Enable verbose output"
    ),
    log_level: str = typer.Option(
        "INFO",
        "--log-level",
        help="Console log level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)",
    ),
    ytm_auth: str = typer.Option(
        "browser.json",
        help="Path to YouTube Music authentication file (default: browser.json)",
    ),
):
    """
    Compare a playlist between two sources/services.
    """
    # Set console log level
    try:
        set_console_log_level(log_level)
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

    # Default target playlist name to source playlist name if not specified
    if not playlist_name_target:
        playlist_name_target = playlist_name_source

    # Create configuration
    config = {
        "similarity": similarity,
        "ytm_auth_file": ytm_auth,
    }

    log = get_logger()
    try:
        # Create source and target
        log.info(f"Initializing {source} source...")
        source_obj = create_source_or_service(source, config)

        log.info(f"Initializing {target} target...")
        target_obj = create_source_or_service(target, config)

        # Get playlists from both source and target
        log.info(f"Fetching playlist '{playlist_name_source}' from {source}...")
        playlist_source = source_obj.get_playlist(playlist_name_source)
        if not playlist_source:
            print(f"Error: Playlist '{playlist_name_source}' not found in {source}")
            sys.exit(1)
        log.info(
            f"Fetched playlist with {len(playlist_source.tracks)} tracks from {source}"
        )

        log.info(f"Fetching playlist '{playlist_name_target}' from {target}...")
        playlist_target = target_obj.get_playlist(playlist_name_target)
        if not playlist_target:
            print(f"Error: Playlist '{playlist_name_target}' not found in {target}")
            sys.exit(1)
        log.info(
            f"Fetched playlist with {len(playlist_target.tracks)} tracks from {target}"
        )

        # Perform comparison
        log.info(f"Comparing playlists...")
        comparer = PlaylistComparer(
            playlist_source=playlist_source,
            playlist_target=playlist_target,
            min_similarity=similarity,
        )
        result: PlaylistCompareResult = comparer.compare_playlists()

        # Print results
        print("\n" + "=" * 50)
        print("Playlist Comparison Results")
        print("=" * 50)
        print(f"Playlist: {result.playlist_name}")
        print(f"Source ({source}): {result.source_track_count} tracks")
        print(f"Target ({target}): {result.target_track_count} tracks")
        print(f"Tracks in both: {len(result.tracks_in_both)}")
        print(f"Only in source: {len(result.tracks_only_in_source)}")
        print(f"Only in target: {len(result.tracks_only_in_target)}")
        print("=" * 50)

        # Print detailed results
        if result.tracks_only_in_source:
            print(f"\nTracks only in {source} ({len(result.tracks_only_in_source)}):")
            for track in result.tracks_only_in_source:
                print(f"  - {track.artist} - {track.title}")

        if result.tracks_only_in_target:
            print(f"\nTracks only in {target} ({len(result.tracks_only_in_target)}):")
            for track in result.tracks_only_in_target:
                print(f"  - {track.artist} - {track.title}")

        # Show matched tracks if verbose
        if verbose and result.tracks_in_both:
            print(f"\nTracks in both ({len(result.tracks_in_both)}):")
            for source_track, target_track in result.tracks_in_both:
                print(
                    f"  - {source_track.artist} - {source_track.title} <-> {target_track.artist} - {target_track.title}"
                )

        print("\nComparison completed successfully")

    except KeyboardInterrupt:
        print("\nComparison interrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"Error: {e}")
        if verbose:
            import traceback

            traceback.print_exc()
        sys.exit(1)
