"""
API routes for song operations in the Music Round application
"""
from flask import Blueprint, jsonify, request, current_app, session, redirect, url_for
from musicround.models import Song, Tag, SongTag, db, Round
from musicround.helpers.metadata import get_song_metadata_by_isrc
from flask_wtf.csrf import CSRFProtect
import traceback  # Add import at the top
import logging
from sqlalchemy import or_
from flask_login import login_required, current_user
import requests  # Import requests for direct API calls

api_bp = Blueprint('api', __name__, url_prefix='/api')

@api_bp.route('/songs/<int:song_id>', methods=['GET', 'PUT', 'DELETE'])
def song_detail(song_id):
    """API endpoint for getting, updating, and deleting song details"""
    song = Song.query.get_or_404(song_id)
    
    if request.method == 'GET':
        # Return song details as JSON
        tag_list = [{'id': tag.id, 'name': tag.name} for tag in song.tags]
        
        return jsonify({
            'id': song.id,
            'title': song.title,
            'artist': song.artist,
            'genre': song.genre,
            'year': song.year,
            'isrc': song.isrc,
            'popularity': song.popularity,
            'used_count': song.used_count,
            'spotify_id': song.spotify_id,
            'deezer_id': song.deezer_id,
            'preview_url': song.preview_url,
            'cover_url': song.cover_url,
            'spotify_preview_url': song.spotify_preview_url,
            'deezer_preview_url': song.deezer_preview_url,
            'apple_preview_url': song.apple_preview_url,
            'youtube_preview_url': song.youtube_preview_url,
            'spotify_cover_url': song.spotify_cover_url,
            'deezer_cover_url': song.deezer_cover_url,
            'apple_cover_url': song.apple_cover_url,
            'metadata_sources': song.metadata_sources,
            'album_name': song.album_name,
            'source': song.source,
            'import_date': song.import_date.isoformat() if song.import_date else None,
            'tags': tag_list,
            # Add all Spotify audio features
            'acousticness': song.acousticness,
            'danceability': song.danceability,
            'energy': song.energy,
            'instrumentalness': song.instrumentalness,
            'key': song.key,
            'liveness': song.liveness,
            'loudness': song.loudness,
            'mode': song.mode,
            'speechiness': song.speechiness,
            'tempo': song.tempo,
            'time_signature': song.time_signature,
            'valence': song.valence,
            'duration_ms': song.duration_ms
        })
    
    elif request.method == 'PUT':
        # Update song details
        data = request.get_json()
        current_app.logger.info(f"Updating song {song_id} with data: {data}")
        
        # Update basic fields
        if data.get('title'):
            song.title = data['title']
        if data.get('artist'):
            song.artist = data['artist']
        if data.get('genre'):
            song.genre = data['genre']
        if data.get('year'):
            song.year = data['year']
        if data.get('isrc'):
            song.isrc = data['isrc']
        if 'popularity' in data and data['popularity'] is not None:
            try:
                song.popularity = int(data['popularity'])
            except (ValueError, TypeError):
                current_app.logger.warning(f"Invalid popularity value: {data['popularity']}")
        
        # Update IDs
        if data.get('spotify_id'):
            song.spotify_id = data['spotify_id']
        if data.get('deezer_id'):
            song.deezer_id = data['deezer_id']
            
        # Update URLs
        if data.get('preview_url'):
            song.preview_url = data['preview_url']
        if data.get('cover_url'):
            song.cover_url = data['cover_url']
            
        # Save changes
        db.session.commit()
        current_app.logger.info(f"Song {song_id} updated successfully")
        
        # Return updated song details including tags
        tag_list = [{'id': tag.id, 'name': tag.name} for tag in song.tags]
        
        return jsonify({
            'id': song.id,
            'title': song.title,
            'artist': song.artist,
            'genre': song.genre,
            'year': song.year,
            'isrc': song.isrc,
            'popularity': song.popularity,
            'preview_url': song.preview_url,
            'cover_url': song.cover_url,
            'tags': tag_list
        })
        
    elif request.method == 'DELETE':
        # Check if the song is used in any rounds before deleting
        rounds_with_song = []
        
        for round_obj in Round.query.all():
            song_ids = round_obj.songs.split(',')
            if str(song_id) in song_ids:
                rounds_with_song.append(round_obj.id)
        
        if rounds_with_song:
            # The song is used in rounds, return error
            return jsonify({
                'error': 'Cannot delete song as it is used in rounds',
                'rounds': rounds_with_song
            }), 400
        
        # Delete the song
        title = song.title
        artist = song.artist
        db.session.delete(song)
        db.session.commit()
        current_app.logger.info(f"Song {song_id} ({title} by {artist}) deleted successfully")
        
        return jsonify({
            'message': f"Song '{title}' by {artist} deleted successfully",
            'id': song_id
        })

@api_bp.route('/songs/<int:song_id>/refresh-metadata', methods=['POST'])
def refresh_song_metadata(song_id):
    """API endpoint for refreshing song metadata"""
    song = Song.query.get_or_404(song_id)
    
    # Check if we have an ISRC code to use for refreshing metadata
    if not song.isrc:
        current_app.logger.warning(f"Cannot refresh metadata for song {song_id} - no ISRC code")
        return jsonify({'error': 'Song has no ISRC code to refresh metadata'}), 400
    
    try:
        # Get fresh metadata using the existing ISRC
        current_app.logger.info(f"=== DEBUG: Starting metadata refresh for song {song_id} with ISRC {song.isrc} ===")
        metadata = get_song_metadata_by_isrc(song.isrc, current_app)
        
        if not metadata:
            current_app.logger.warning(f"No metadata found for ISRC {song.isrc}")
            return jsonify({'error': 'No metadata found for this ISRC'}), 404
        
        # Debug the metadata received
        current_app.logger.info(f"DEBUG: Received metadata structure: {type(metadata).__name__}")
        for key, value in metadata.items():
            value_type = type(value).__name__
            value_str = str(value)
            if len(value_str) > 100:
                value_str = value_str[:100] + "..."
            current_app.logger.info(f"DEBUG: Metadata key '{key}' = {value_str} (type: {value_type})")
        
        # Update song with new metadata
        if metadata.get('title'):
            current_app.logger.info(f"DEBUG: Updating title to '{metadata['title']}'")
            song.title = metadata['title']
        if metadata.get('artist_name'):
            current_app.logger.info(f"DEBUG: Updating artist to '{metadata['artist_name']}'")
            song.artist = metadata['artist_name']
        if metadata.get('genre'):
            current_app.logger.info(f"DEBUG: Updating genre to '{metadata['genre']}' (type: {type(metadata['genre']).__name__})")
            song.genre = metadata['genre']
            
            # Import helper for creating tags from genre
            from musicround.helpers.import_helper import ImportHelper
            ImportHelper.create_tags_from_genre(song, metadata['genre'])
            
        if metadata.get('year'):
            current_app.logger.info(f"DEBUG: Updating year to '{metadata['year']}'")
            song.year = metadata['year']
        
        # Check additional metadata for genres and add tags from there too
        if 'genres' in metadata:
            current_app.logger.info(f"DEBUG: Creating tags from additional genres")
            from musicround.helpers.import_helper import ImportHelper
            ImportHelper.create_tags_from_genre(song, metadata['genres'])
        
        # Update URLs if available
        if metadata.get('preview_url'):
            current_app.logger.info(f"DEBUG: Updating preview_url")
            song.preview_url = metadata['preview_url']
        if metadata.get('cover_url'):
            current_app.logger.info(f"DEBUG: Updating cover_url")
            song.cover_url = metadata['cover_url']
        
        # Update platform-specific IDs if available
        if metadata.get('spotify_id'):
            song.spotify_id = metadata['spotify_id']
        if metadata.get('deezer_id'):
            song.deezer_id = metadata['deezer_id']
            
        # Update platform-specific preview URLs
        if metadata.get('spotify_preview_url'):
            song.spotify_preview_url = metadata['spotify_preview_url']
        if metadata.get('deezer_preview_url'):
            song.deezer_preview_url = metadata['deezer_preview_url']
        if metadata.get('apple_preview_url'):
            song.apple_preview_url = metadata['apple_preview_url']
        if metadata.get('youtube_preview_url'):
            song.youtube_preview_url = metadata['youtube_preview_url']
            
        # Update platform-specific cover URLs
        if metadata.get('spotify_cover_url'):
            song.spotify_cover_url = metadata['spotify_cover_url']
        if metadata.get('deezer_cover_url'):
            song.deezer_cover_url = metadata['deezer_cover_url']
        if metadata.get('apple_cover_url'):
            song.apple_cover_url = metadata['apple_cover_url']
            
        # Update popularity if available
        if metadata.get('popularity') is not None:
            song.popularity = metadata['popularity']
            
        # Store metadata sources
        if metadata.get('sources'):
            try:
                sources_str = ','.join(metadata['sources'])
                current_app.logger.info(f"DEBUG: Setting metadata_sources to '{sources_str}'")
                song.metadata_sources = sources_str
            except Exception as source_error:
                current_app.logger.error(f"DEBUG: Error joining sources: {source_error}")
                current_app.logger.error(f"DEBUG: Sources value: {metadata['sources']} (type: {type(metadata['sources']).__name__})")
        
        # Save changes to the database
        try:
            current_app.logger.info("DEBUG: Committing changes to database")
            db.session.commit()
            current_app.logger.info(f"Metadata for song {song_id} updated successfully with sources: {metadata.get('sources')}")
        except Exception as db_error:
            db.session.rollback()
            current_app.logger.error(f"DEBUG: Database commit error: {db_error}")
            current_app.logger.error(f"DEBUG: Traceback: {traceback.format_exc()}")
            return jsonify({'error': f"Database error: {str(db_error)}"}), 500
        
        # Return updated song details including tags
        tag_list = [{'id': tag.id, 'name': tag.name} for tag in song.tags]
        
        return jsonify({
            'id': song.id,
            'title': song.title,
            'artist': song.artist,
            'genre': song.genre,
            'year': song.year,
            'isrc': song.isrc,
            'popularity': song.popularity,
            'preview_url': song.preview_url,
            'cover_url': song.cover_url,
            'spotify_id': song.spotify_id,
            'deezer_id': song.deezer_id,
            'spotify_preview_url': song.spotify_preview_url,
            'deezer_preview_url': song.deezer_preview_url,
            'apple_preview_url': song.apple_preview_url,
            'youtube_preview_url': song.youtube_preview_url,
            'metadata_sources': song.metadata_sources,
            'tags': tag_list
        })
        
    except Exception as e:
        current_app.logger.error(f"Error refreshing metadata for song {song_id}: {str(e)}")
        current_app.logger.error(f"Full traceback: {traceback.format_exc()}")
        return jsonify({'error': str(e)}), 500

# New API routes for tag operations

@api_bp.route('/tags', methods=['GET'])
def list_tags():
    """Get all available tags"""
    tags = Tag.query.order_by(Tag.name).all()
    return jsonify({
        'tags': [{'id': tag.id, 'name': tag.name} for tag in tags]
    })

@api_bp.route('/tags', methods=['POST'])
def create_tag():
    """Create a new tag"""
    data = request.get_json()
    
    if not data or not data.get('name'):
        return jsonify({'error': 'Tag name is required'}), 400
    
    tag_name = data['name'].strip()
    
    # Check if tag already exists
    existing_tag = Tag.query.filter(Tag.name.ilike(tag_name)).first()
    if existing_tag:
        return jsonify({
            'message': 'Tag already exists',
            'tag': {'id': existing_tag.id, 'name': existing_tag.name}
        })
    
    # Create new tag
    new_tag = Tag(name=tag_name)
    db.session.add(new_tag)
    db.session.commit()
    
    return jsonify({
        'message': 'Tag created successfully',
        'tag': {'id': new_tag.id, 'name': new_tag.name}
    }), 201

@api_bp.route('/songs/<int:song_id>/tags', methods=['GET'])
def get_song_tags(song_id):
    """Get all tags for a specific song"""
    song = Song.query.get_or_404(song_id)
    
    return jsonify({
        'song_id': song_id,
        'tags': [{'id': tag.id, 'name': tag.name} for tag in song.tags]
    })

@api_bp.route('/songs/<int:song_id>/tags', methods=['POST'])
def add_tag_to_song(song_id):
    """Add a tag to a song"""
    song = Song.query.get_or_404(song_id)
    data = request.get_json()
    
    if not data:
        return jsonify({'error': 'No data provided'}), 400
    
    # Check if we're adding an existing tag or creating a new one
    if data.get('tag_id'):
        # Add existing tag
        tag = Tag.query.get_or_404(data['tag_id'])
    elif data.get('tag_name'):
        # Find or create tag
        tag_name = data['tag_name'].strip()
        tag = Tag.query.filter(Tag.name.ilike(tag_name)).first()
        
        if not tag:
            tag = Tag(name=tag_name)
            db.session.add(tag)
            db.session.commit()
    else:
        return jsonify({'error': 'Either tag_id or tag_name must be provided'}), 400
    
    # Check if the song already has this tag
    if tag in song.tags:
        return jsonify({
            'message': f"Song '{song.title}' already has tag '{tag.name}'",
            'song_id': song_id,
            'tag': {'id': tag.id, 'name': tag.name}
        })
    
    # Add tag to song
    song.tags.append(tag)
    db.session.commit()
    
    return jsonify({
        'message': f"Tag '{tag.name}' added to song '{song.title}'",
        'song_id': song_id,
        'tag': {'id': tag.id, 'name': tag.name}
    })

@api_bp.route('/songs/<int:song_id>/tags/<int:tag_id>', methods=['DELETE'])
def remove_tag_from_song(song_id, tag_id):
    """Remove a tag from a song"""
    song = Song.query.get_or_404(song_id)
    tag = Tag.query.get_or_404(tag_id)
    
    # Check if the song has this tag
    if tag not in song.tags:
        return jsonify({
            'message': f"Song '{song.title}' doesn't have tag '{tag.name}'",
            'song_id': song_id,
            'tag_id': tag_id
        })
    
    # Remove tag from song
    song.tags.remove(tag)
    db.session.commit()
    
    return jsonify({
        'message': f"Tag '{tag.name}' removed from song '{song.title}'",
        'song_id': song_id,
        'tag_id': tag_id
    })

@api_bp.route('/tags/<int:tag_id>', methods=['GET'])
def get_songs_by_tag(tag_id):
    """Get all songs with a specific tag"""
    tag = Tag.query.get_or_404(tag_id)
    
    songs = []
    for song in tag.songs:
        songs.append({
            'id': song.id,
            'title': song.title,
            'artist': song.artist
        })
    
    return jsonify({
        'tag': {'id': tag.id, 'name': tag.name},
        'song_count': len(songs),
        'songs': songs
    })

@api_bp.route('/spotify/album/<album_id>', methods=['GET'])
def get_spotify_album(album_id):
    try:
        # Check for Spotify access token
        if 'spotify_token' not in session:  # Assuming token will be stored as 'spotify_token' in session
            return jsonify({'error': 'Spotify token not found in session. Please authenticate with Spotify.'}), 401
        
        access_token = session['spotify_token']
        headers = {
            'Authorization': f'Bearer {access_token}'
        }
        
        # Get album details
        album_url = f'https://api.spotify.com/v1/albums/{album_id}'
        album_response = requests.get(album_url, headers=headers)
        album_response.raise_for_status()  # Raise an exception for HTTP errors
        album = album_response.json()

        # Get album tracks
        album_tracks_url = f'https://api.spotify.com/v1/albums/{album_id}/tracks?limit=50'
        album_tracks_response = requests.get(album_tracks_url, headers=headers)
        album_tracks_response.raise_for_status()
        album_tracks = album_tracks_response.json()
        
        # Format tracks
        tracks = []
        for track in album_tracks['items']:
            artist_names = [artist['name'] for artist in track['artists']]
            tracks.append({
                'name': track['name'],
                'artist': ', '.join(artist_names),
                'duration': track['duration_ms'],
                'track_number': track['track_number']
            })
        
        # Format album response
        album_data = {
            'id': album['id'],
            'name': album['name'],
            'artist': ', '.join([artist['name'] for artist in album['artists']]),
            'release_date': album['release_date'],
            'image_url': album['images'][0]['url'] if album['images'] else None,
            'total_tracks': album['total_tracks'],
            'tracks': tracks
        }
        
        return jsonify(album_data)
    except requests.exceptions.HTTPError as http_err:
        current_app.logger.error(f"HTTP error fetching Spotify album: {http_err} - {http_err.response.text}")
        return jsonify({'error': f'Spotify API error: {http_err.response.status_code}', 'details': http_err.response.json() if http_err.response.content else None}), http_err.response.status_code
    except Exception as e:
        current_app.logger.error(f"Error fetching Spotify album: {str(e)}")
        current_app.logger.error(f"Full traceback: {traceback.format_exc()}")
        return jsonify({'error': 'Unable to fetch album details'}), 500

@api_bp.route('/spotify/playlist/<playlist_id>', methods=['GET'])
def get_spotify_playlist(playlist_id):
    try:
        # Check for Spotify access token
        if 'spotify_token' not in session:  # Assuming token will be stored as 'spotify_token' in session
            return jsonify({'error': 'Spotify token not found in session. Please authenticate with Spotify.'}), 401

        access_token = session['spotify_token']
        headers = {
            'Authorization': f'Bearer {access_token}'
        }
          # Get playlist details
        playlist_url = f'https://api.spotify.com/v1/playlists/{playlist_id}'
        playlist_response = requests.get(playlist_url, headers=headers)
        playlist_response.raise_for_status()
        playlist = playlist_response.json()
        
        # Format tracks with pagination support
        tracks = []
        tracks_url = playlist['tracks']['href']  # Get the tracks URL for pagination
        offset = 0
        limit = 100  # Maximum limit for tracks per request
        total_tracks = playlist['tracks']['total']
        
        current_app.logger.info(f"Fetching {total_tracks} tracks from playlist {playlist_id}")
        
        # Fetch all tracks with pagination
        while True:
            # Construct URL with pagination parameters
            paginated_url = f"{tracks_url}?offset={offset}&limit={limit}"
            current_app.logger.info(f"Fetching tracks from offset {offset}, limit {limit}")
            
            tracks_response = requests.get(paginated_url, headers=headers)
            tracks_response.raise_for_status()
            tracks_data = tracks_response.json()
            
            # Process tracks from this page
            items = tracks_data.get('items', [])
            for item in items:
                if not item['track']:  # Handle cases where track might be None (e.g., local files in playlist)
                    continue
                    
                track = item['track']
                artist_names = [artist['name'] for artist in track['artists']]
                tracks.append({
                    'name': track['name'],
                    'artist': ', '.join(artist_names),
                    'duration': track['duration_ms'],
                    'album': track['album']['name'] if track.get('album') else ''
                })
            
            current_app.logger.info(f"Processed {len(items)} tracks, total so far: {len(tracks)}")
              # Check if we have more pages to fetch
            if not tracks_data.get('next') or len(items) < limit:
                current_app.logger.info(f"Finished fetching all tracks. Total tracks: {len(tracks)}")
                break
                
            offset += limit
        
        # Format playlist response
        playlist_data = {
            'id': playlist['id'],
            'name': playlist['name'],
            'description': playlist['description'],
            'owner': playlist['owner']['display_name'] or playlist['owner']['id'],
            'image_url': playlist['images'][0]['url'] if playlist['images'] else None,
            'followers': playlist['followers']['total'] if playlist.get('followers') else 0,
            'tracks': tracks
        }
        
        return jsonify(playlist_data)
    except requests.exceptions.HTTPError as http_err:
        current_app.logger.error(f"HTTP error fetching Spotify playlist: {http_err} - {http_err.response.text}")
        return jsonify({'error': f'Spotify API error: {http_err.response.status_code}', 'details': http_err.response.json() if http_err.response.content else None}), http_err.response.status_code
    except Exception as e:
        current_app.logger.error(f"Error fetching Spotify playlist: {str(e)}")
        current_app.logger.error(f"Full traceback: {traceback.format_exc()}")
        return jsonify({'error': 'Unable to fetch playlist details'}), 500

@api_bp.route('/spotify/search', methods=['GET'])
@login_required
def spotify_search():
    query = request.args.get('q', '')
    search_type = request.args.get('type', 'track,artist,album')  # Default to searching for tracks, artists, and albums
    limit = request.args.get('limit', 20)

    if not query:
        return jsonify({"error": "Search query cannot be empty"}), 400

    if 'spotify_token' not in session:
        return jsonify({'error': 'Spotify token not found in session. Please authenticate with Spotify.'}), 401

    access_token = session['spotify_token']
    headers = {
        'Authorization': f'Bearer {access_token}'
    }
    params = {
        'q': query,
        'type': search_type,
        'limit': limit
    }

    try:
        search_url = 'https://api.spotify.com/v1/search'
        response = requests.get(search_url, headers=headers, params=params)
        response.raise_for_status()  # Raise an exception for HTTP errors
        search_results = response.json()
        return jsonify(search_results)

    except requests.exceptions.HTTPError as http_err:
        current_app.logger.error(f"HTTP error during Spotify search: {http_err} - {http_err.response.text}")
        return jsonify({'error': f'Spotify API error: {http_err.response.status_code}', 'details': http_err.response.json() if http_err.response.content else None}), http_err.response.status_code
    except Exception as e:
        current_app.logger.error(f"Error during Spotify search: {str(e)}")
        current_app.logger.error(f"Full traceback: {traceback.format_exc()}")
        return jsonify({'error': 'Unable to perform Spotify search'}), 500

@api_bp.route('/deezer/album/<album_id>', methods=['GET'])
def get_deezer_album(album_id):
    try:
        # Get album details from Deezer using the DeezerClient instance from Flask app config
        deezer_client = current_app.config.get('deezer')  # Using correct config key 'deezer'
        album = deezer_client.get_album(album_id)
        tracks = deezer_client.get_album_tracks(album_id)
        
        # Format tracks
        formatted_tracks = []
        for track in tracks:
            formatted_tracks.append({
                'name': track['title'],
                'artist': track['artist']['name'],
                'duration': track['duration'] * 1000,  # Convert to ms for consistency
                'track_number': track.get('track_position', 0)
            })
        
        # Format album response
        album_data = {
            'id': album['id'],
            'name': album['title'],
            'artist': album['artist']['name'],
            'release_date': album.get('release_date', ''),
            'image_url': album.get('cover_xl') or album.get('cover_big') or album.get('cover'),
            'total_tracks': album.get('nb_tracks', len(formatted_tracks)),
            'tracks': formatted_tracks
        }
        
        return jsonify(album_data)
    except Exception as e:
        current_app.logger.error(f"Error fetching Deezer album: {str(e)}")
        return jsonify({'error': 'Unable to fetch album details'}), 500

@api_bp.route('/deezer/playlist/<playlist_id>', methods=['GET'])
def get_deezer_playlist(playlist_id):
    try:
        # Get playlist details from Deezer using the DeezerClient instance from Flask app config
        deezer_client = current_app.config.get('deezer')  # Using correct config key 'deezer'
        playlist = deezer_client.get_playlist(playlist_id)
        tracks = deezer_client.get_playlist_tracks(playlist_id)
        
        # Format tracks
        formatted_tracks = []
        for track in tracks:
            formatted_tracks.append({
                'name': track['title'],
                'artist': track['artist']['name'],
                'duration': track['duration'] * 1000,  # Convert to ms for consistency
                'album': track['album']['title'] if 'album' in track else ''
            })
        
        # Format playlist response
        playlist_data = {
            'id': playlist['id'],
            'name': playlist['title'],
            'description': playlist.get('description', ''),
            'owner': playlist.get('creator', {}).get('name', 'Unknown'),
            'image_url': playlist.get('picture_xl') or playlist.get('picture_big') or playlist.get('picture'),
            'followers': playlist.get('fans', 0),
            'tracks': formatted_tracks
        }
        
        return jsonify(playlist_data)
    except Exception as e:
        current_app.logger.error(f"Error fetching Deezer playlist: {str(e)}")
        return jsonify({'error': 'Unable to fetch playlist details'}), 500

@api_bp.route('/songs/search')
@login_required
def search_songs():
    """Search for songs by title or artist"""
    query = request.args.get('q', '')
    if not query or len(query) < 2:
        return jsonify([])
    
    current_app.logger.info(f"User {current_user.username} searching for songs with query: {query}")
        
    # Search for songs by title or artist
    songs = Song.query.filter(
        or_(
            Song.title.ilike(f'%{query}%'),
            Song.artist.ilike(f'%{query}%')
        )
    ).limit(20).all()
    
    # Convert songs to JSON
    results = []
    for song in songs:
        results.append({
            'id': song.id,
            'title': song.title,
            'artist': song.artist,
            'year': song.year,
            'genre': song.genre,
            'cover_url': song.cover_url,
            'preview_url': song.preview_url
        })
    
    return jsonify(results)

@api_bp.route('/songs/update-audio-features', methods=['POST'])
@login_required
def update_audio_features():
    """Update audio features for Spotify songs in the database"""
    # Get parameters from the request
    batch_size = request.json.get('batch_size', 50)  # Process in batches to avoid timeouts
    process_all = request.json.get('process_all', False)
    process_specific = request.json.get('process_specific', False)
    song_ids = request.json.get('song_ids', [])
    
    # If specific song IDs are provided, query those songs
    if process_specific and song_ids:
        query = Song.query.filter(
            Song.id.in_(song_ids),
            Song.spotify_id.isnot(None)  # Only process songs with Spotify IDs
        )
    else:
        # Query songs that have Spotify IDs but no audio features
        query = Song.query.filter(
            Song.spotify_id.isnot(None)  # Only process songs with Spotify IDs
        )
    
        # If not processing all, only select songs without audio features
        if not process_all:
            query = query.filter(Song.acousticness.is_(None))
    
    # Count total songs to process
    total_songs = query.count()
    
    if total_songs == 0:
        return jsonify({
            'success': True,
            'message': 'No songs found that need audio features.',
            'processed': 0,
            'total': 0
        })
    
    # Get bearer token from session
    bearer_token = session.get('direct_bearer_token')
    
    # If no direct bearer token found, try to use the standard access token as fallback
    if not bearer_token:
        bearer_token = session.get('access_token')
        if not bearer_token:
            return jsonify({
                'success': False,
                'message': 'No Spotify authentication token found in session.',
                'error': 'SPOTIFY_TOKEN_NOT_FOUND'
            }), 401
    
    # Create an instance of the SpotifyDirectClient for this request
    try:
        # Initialize our custom Spotify client with the bearer token
        from musicround.helpers.spotify_direct import SpotifyDirectClient
        direct_sp = SpotifyDirectClient(bearer_token=bearer_token)
    except Exception as e:
        current_app.logger.error(f"Error initializing SpotifyDirectClient: {str(e)}")
        return jsonify({
            'success': False,
            'message': 'Failed to initialize Spotify client',
            'error': str(e)
        }), 500
    
    # Process songs in batches
    processed_count = 0
    error_count = 0
    
    # Get all matching songs (limit to reasonable number to prevent timeouts)
    songs = query.limit(1000).all()
    
    # Process in batches of batch_size
    for i in range(0, len(songs), batch_size):
        batch = songs[i:i+batch_size]
        track_ids = [song.spotify_id for song in batch]
        
        # Get audio features for this batch
        try:
            # Use our custom SpotifyDirectClient for batch audio features
            features = direct_sp.get_tracks_audio_features(track_ids)
            
            if not features:
                current_app.logger.warning(f"No audio features returned for batch {i//batch_size + 1}")
                error_count += len(batch)
                continue
                
            # Map features to songs by Spotify ID
            features_dict = {feature['id']: feature for feature in features if feature}
            
            # Update each song with its audio features
            for song in batch:
                if song.spotify_id in features_dict:
                    feature = features_dict[song.spotify_id]
                    
                    # Update song with audio features
                    song.acousticness = feature.get('acousticness')
                    song.danceability = feature.get('danceability')
                    song.energy = feature.get('energy')
                    song.instrumentalness = feature.get('instrumentalness')
                    song.key = feature.get('key')
                    song.liveness = feature.get('liveness')
                    song.loudness = feature.get('loudness')
                    song.mode = feature.get('mode')
                    song.speechiness = feature.get('speechiness')
                    song.tempo = feature.get('tempo')
                    song.time_signature = feature.get('time_signature')
                    song.valence = feature.get('valence')
                    song.duration_ms = feature.get('duration_ms')
                    song.analysis_url = feature.get('analysis_url')
                    
                    # Add audio features to metadata sources if not already present
                    if song.metadata_sources:
                        sources = song.metadata_sources.split(',')
                        if 'audio_features' not in sources:
                            sources.append('audio_features')
                            song.metadata_sources = ','.join(sources)
                    else:
                        song.metadata_sources = 'audio_features'
                    
                    processed_count += 1
                else:
                    current_app.logger.warning(f"No audio features found for song {song.id} ({song.spotify_id})")
                    error_count += 1
            
            # Save all changes to database
            db.session.commit()
            
        except Exception as e:
            db.session.rollback()
            current_app.logger.error(f"Error processing audio features batch: {str(e)}")
            error_count += len(batch)
    
    return jsonify({
        'success': True,
        'message': f'Successfully updated audio features for {processed_count} songs. {error_count} errors.',
        'processed': processed_count,
        'errors': error_count,
        'total': total_songs
    })

@api_bp.route('/dropbox/folders', methods=['GET'])
@login_required
def list_dropbox_folders():
    """List folders from user's Dropbox account for the folder browser"""
    from flask_login import current_user
    from musicround.helpers.dropbox_helper import get_current_user_dropbox_token
    import requests
    import json
    import traceback
    
    # Check if user has Dropbox connected
    if not current_user.dropbox_token:
        current_app.logger.error("Dropbox folder listing failed: User has no Dropbox token")
        return jsonify({'error': 'Dropbox account not connected'}), 401
        
    # Get Dropbox token
    token = get_current_user_dropbox_token()
    if not token:
        current_app.logger.error("Dropbox folder listing failed: Failed to get valid token")
        return jsonify({'error': 'Failed to get valid Dropbox token'}), 401
    
    # Get the path from query params, default to empty string (root)
    path = request.args.get('path', '')
    
    # Fix path format for Dropbox API
    # Dropbox API requires empty string for root, not "/"
    if path == '/' or not path:
        path = ""
        display_path = "/"
    else:
        display_path = path
        
    current_app.logger.info(f"Listing Dropbox folders for path: {display_path}")
        
    try:
        # Call Dropbox API to list folders
        headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }
        
        # Use the list_folder API with recursive=False to get only immediate children
        data = {
            'path': path,
            'recursive': False,
            'include_deleted': False,
            'include_has_explicit_shared_members': False,
            'include_mounted_folders': True,
            'include_non_downloadable_files': False
        }
        
        current_app.logger.debug(f"Sending request to Dropbox API: {json.dumps(data)}")
        current_app.logger.debug(f"Using token: {token[:5]}...{token[-5:] if len(token) > 10 else ''}")
        
        response = requests.post(
            'https://api.dropboxapi.com/2/files/list_folder',
            headers=headers,
            json=data
        )
        
        current_app.logger.debug(f"Dropbox API response status: {response.status_code}")
        
        if response.status_code != 200:
            current_app.logger.error(f"Dropbox API error: {response.status_code}, Response: {response.text}")
            
            # Check for specific error types to provide better messages
            try:
                error_data = response.json()
                error_message = f"Dropbox API error: {response.status_code}"
                
                # Handle "not_found" error by trying to create the folder if it's not root
                if (response.status_code == 409 and 
                    "not_found" in response.text and 
                    path != ""):
                    current_app.logger.info(f"Folder {display_path} doesn't exist, showing root folder instead")
                    # Return root folder with a note about the folder not existing
                    return list_root_folders(token, display_path)
                
                if 'error_summary' in error_data:
                    error_message += f": {error_data['error_summary']}"
                
                # Handle common errors
                if response.status_code == 401:
                    # Try to refresh the token and retry once
                    current_app.logger.info("Attempting to refresh Dropbox token and retry")
                    from musicround.helpers.dropbox_helper import refresh_dropbox_token
                    
                    if current_user.dropbox_refresh_token:
                        new_token_info = refresh_dropbox_token(current_user.dropbox_refresh_token)
                        if new_token_info and 'access_token' in new_token_info:
                            # Try again with the new token
                            headers['Authorization'] = f"Bearer {new_token_info['access_token']}"
                            response = requests.post(
                                'https://api.dropboxapi.com/2/files/list_folder',
                                headers=headers,
                                json=data
                            )
                            
                            if response.status_code == 200:
                                # Success! Continue with processing
                                current_app.logger.info("Successfully refreshed token and retrieved folders")
                            else:
                                current_app.logger.error(f"Still failed after token refresh: {response.status_code}, {response.text}")
                                return jsonify({'error': error_message, 'details': error_data}), response.status_code
                        else:
                            current_app.logger.error("Token refresh failed")
                
                if response.status_code != 200:  # If we're still having an error
                    return jsonify({'error': error_message, 'details': error_data}), response.status_code
                
            except Exception as json_error:
                current_app.logger.error(f"Error parsing Dropbox error response: {str(json_error)}")
                return jsonify({'error': f'Dropbox API error: {response.status_code}', 'raw_response': response.text}), 500
        
        # Process the successful response
        result = response.json()
        current_app.logger.debug(f"Received Dropbox response: {json.dumps(result)[:1000] if len(json.dumps(result)) > 1000 else json.dumps(result)}")
            
        # Filter to only include folders
        folders = []
        for entry in result.get('entries', []):
            if entry.get('.tag') == 'folder':
                folders.append({
                    'name': entry.get('name', ''),
                    'path_display': entry.get('path_display', ''),
                    'id': entry.get('id', '')
                })
        
        current_app.logger.info(f"Successfully listed {len(folders)} folders in path '{display_path}'")
                
        return jsonify({
            'path': display_path,
            'folders': folders
        })
        
    except Exception as e:
        error_traceback = traceback.format_exc()
        current_app.logger.error(f"Error listing Dropbox folders: {str(e)}")
        current_app.logger.error(f"Traceback: {error_traceback}")
        return jsonify({
            'error': str(e),
            'traceback': error_traceback,
            'message': 'An unexpected error occurred while listing Dropbox folders'
        }), 500

@api_bp.route('/dropbox/create-folder', methods=['POST'])
@login_required
def create_dropbox_folder():
    """Create a new folder in the user's Dropbox account"""
    from flask_login import current_user
    from musicround.helpers.dropbox_helper import get_current_user_dropbox_token
    import requests
    import json
    import traceback
    
    # Check if user has Dropbox connected
    if not current_user.dropbox_token:
        current_app.logger.error("Dropbox folder creation failed: User has no Dropbox token")
        return jsonify({'error': 'Dropbox account not connected'}), 401
        
    # Get Dropbox token
    token = get_current_user_dropbox_token()
    if not token:
        current_app.logger.error("Dropbox folder creation failed: Failed to get valid token")
        return jsonify({'error': 'Failed to get valid Dropbox token'}), 401
    
    # Get path and new folder name from request
    data = request.get_json()
    if not data or 'parent_path' not in data or 'folder_name' not in data:
        return jsonify({'error': 'Missing required parameters: parent_path and folder_name'}), 400
    
    parent_path = data['parent_path']
    folder_name = data['folder_name'].strip()
    
    # Validate folder name - basic validation
    if not folder_name:
        return jsonify({'error': 'Folder name cannot be empty'}), 400
        
    if any(char in folder_name for char in ['/', '\\', ':', '*', '?', '"', '<', '>', '|']):
        return jsonify({'error': 'Folder name contains invalid characters'}), 400
    
    # Construct full path
    # If parent is root, we need special handling
    if parent_path == '/' or parent_path == '':
        full_path = '/' + folder_name
    else:
        full_path = parent_path + '/' + folder_name
    
    current_app.logger.info(f"Creating Dropbox folder: {full_path}")
    
    try:
        # Call Dropbox API to create folder
        headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }
        
        data = {
            'path': full_path,
            'autorename': False
        }
        
        current_app.logger.debug(f"Sending request to Dropbox API: {json.dumps(data)}")
        
        response = requests.post(
            'https://api.dropboxapi.com/2/files/create_folder_v2',
            headers=headers,
            json=data
        )
        
        current_app.logger.debug(f"Dropbox API response status: {response.status_code}")
        
        if response.status_code != 200:
            current_app.logger.error(f"Dropbox API error: {response.status_code}, Response: {response.text}")
            
            try:
                error_data = response.json()
                error_message = f"Dropbox API error: {response.status_code}"
                
                if 'error_summary' in error_data:
                    error_message += f": {error_data['error_summary']}"
                
                # Special handling for conflict (folder already exists)
                if response.status_code == 409 and 'conflict' in response.text:
                    return jsonify({
                        'error': 'A folder with this name already exists',
                        'details': error_data
                    }), 409
                
                return jsonify({'error': error_message, 'details': error_data}), response.status_code
                
            except Exception as json_error:
                current_app.logger.error(f"Error parsing Dropbox error response: {str(json_error)}")
                return jsonify({
                    'error': f'Dropbox API error: {response.status_code}',
                    'raw_response': response.text
                }), 500
        
        # Process the successful response
        result = response.json()
        current_app.logger.debug(f"Received Dropbox response: {json.dumps(result)}")
        
        # Extract metadata from result
        metadata = result.get('metadata', {})
        
        current_app.logger.info(f"Successfully created folder: {full_path}")
        
        return jsonify({
            'success': True,
            'path': metadata.get('path_display', full_path),
            'name': metadata.get('name', folder_name),
            'id': metadata.get('id', '')
        })
        
    except Exception as e:
        error_traceback = traceback.format_exc()
        current_app.logger.error(f"Error creating Dropbox folder: {str(e)}")
        current_app.logger.error(f"Traceback: {error_traceback}")
        return jsonify({
            'error': str(e),
            'traceback': error_traceback,
            'message': 'An unexpected error occurred while creating Dropbox folder'
        }), 500

def list_root_folders(token, attempted_path=None):
    """Get root folders as a fallback when requested path doesn't exist"""
    import requests
    import json
    
    try:
        # Call Dropbox API to list root folders
        headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }
        
        data = {
            'path': "",  # Empty string for root
            'recursive': False,
            'include_deleted': False,
            'include_has_explicit_shared_members': False,
            'include_mounted_folders': True,
            'include_non_downloadable_files': False
        }
        
        current_app.logger.debug("Listing root folders as fallback")
        
        response = requests.post(
            'https://api.dropboxapi.com/2/files/list_folder',
            headers=headers,
            json=data
        )
        
        if response.status_code != 200:
            current_app.logger.error(f"Root folder listing failed: {response.status_code}, {response.text}")
            return jsonify({
                'error': f'Could not list root folders: {response.status_code}',
                'attempted_path': attempted_path
            }), response.status_code
        
        # Process the successful response
        result = response.json()
        
        # Filter to only include folders
        folders = []
        for entry in result.get('entries', []):
            if entry.get('.tag') == 'folder':
                folders.append({
                    'name': entry.get('name', ''),
                    'path_display': entry.get('path_display', ''),
                    'id': entry.get('id', '')
                })
        
        message = None
        if attempted_path:
            message = f"Folder '{attempted_path}' doesn't exist. Showing root folder instead."
        
        return jsonify({
            'path': '/',
            'folders': folders,
            'warning': message
        })
        
    except Exception as e:
        current_app.logger.error(f"Error listing root folders: {str(e)}")
        return jsonify({
            'error': f'Error listing root folders: {str(e)}',
            'attempted_path': attempted_path
        }), 500