import sys
from typing import Optional, Any

import typer

from pushtunes.sources.csv import CSVSource
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


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 == "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']}")
    print(f"Skipped (already in library): {stats['skipped_existing']}")
    print(f"Skipped (already in library, cached): {stats['skipped_existing_cached']}")
    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.services.album_pusher import AlbumPusher, AlbumResult, Status


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,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"
    ),
):
    """
    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
        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"\n✓ Successfully 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 == Status.added),
            "mapped": sum(1 for r in results if r.status == Status.mapped),
            "skipped_existing": sum(
                1 for r in results if r.status == Status.already_in_library
            ),
            "skipped_existing_cached": sum(
                1 for r in results if r.status == Status.already_in_library_cache
            ),
            "skipped_not_found": sum(
                1 for r in results if r.status == Status.not_found
            ),
            "skipped_low_similarity": sum(
                1 for r in results if r.status == Status.similarity_too_low
            ),
            "skipped_filtered": sum(1 for r in results if r.status == Status.filtered),
            "errors": sum(1 for r in results if r.status == Status.error),
        }

        print_stats(stats, "albums")

        # 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)

        # 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, Status as TrackStatus
from pushtunes.services.playlist_pusher import PlaylistPusher, PlaylistResult, Status as PlaylistStatus, ConflictMode, pretty_print_track_result


def push_tracks(
    source: str = typer.Option(..., "--from", help="Source ('subsonic', 'jellyfin', or 'csv')"),
    target: str = typer.Option(..., "--to", help="Target ('spotify' 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,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"
    ),
):
    """
    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
        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"\n✓ Successfully 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 == TrackStatus.added),
            "mapped": sum(1 for r in results if r.status == TrackStatus.mapped),
            "skipped_existing": sum(
                1 for r in results if r.status == TrackStatus.already_in_library
            ),
            "skipped_existing_cached": sum(
                1 for r in results if r.status == TrackStatus.already_in_library_cache
            ),
            "skipped_not_found": sum(
                1 for r in results if r.status == TrackStatus.not_found
            ),
            "skipped_low_similarity": sum(
                1 for r in results if r.status == TrackStatus.similarity_too_low
            ),
            "skipped_filtered": sum(1 for r in results if r.status == TrackStatus.filtered),
            "errors": sum(1 for r in results if r.status == TrackStatus.error),
        }

        print_stats(stats, "tracks")

        # 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)

        # 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', 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)"),
    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)"
    ),
    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)

        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"\n✓ Successfully 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,
        )
        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 == PlaylistStatus.matched),
            "not_found": sum(1 for r in result.track_results if r.status == PlaylistStatus.not_found),
            "similarity_too_low": sum(1 for r in result.track_results if r.status == PlaylistStatus.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"✗ {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)
