#!/usr/bin/python3

from spotipy import Spotify
from deezspot.exceptions import InvalidLink
from spotipy.exceptions import SpotifyException
from spotipy.oauth2 import SpotifyClientCredentials
import os

class Spo:
    __error_codes = [404, 400]
    
    # Class-level API instance and credentials
    __api = None
    __client_id = None
    __client_secret = None
    __initialized = False

    @classmethod
    def __init__(cls, client_id, client_secret):
        """
        Initialize the Spotify API client.
        
        Args:
            client_id (str): Spotify API client ID.
            client_secret (str): Spotify API client secret.
        """
        if not client_id or not client_secret:
            raise ValueError("Spotify API credentials required. Provide client_id and client_secret.")
            
        client_credentials_manager = SpotifyClientCredentials(
            client_id=client_id,
            client_secret=client_secret
        )
        
        # Store the credentials and API instance
        cls.__client_id = client_id
        cls.__client_secret = client_secret
        cls.__api = Spotify(
            auth_manager=client_credentials_manager
        )
        cls.__initialized = True

    @classmethod
    def __check_initialized(cls):
        """Check if the class has been initialized with credentials"""
        if not cls.__initialized:
            raise ValueError("Spotify API not initialized. Call Spo.__init__(client_id, client_secret) first.")

    @classmethod
    def __get_api(cls, client_id=None, client_secret=None):
        """
        Get a Spotify API instance with the provided credentials or use stored credentials.
        
        Args:
            client_id (str, optional): Spotify API client ID
            client_secret (str, optional): Spotify API client secret
            
        Returns:
            A Spotify API instance
        """
        # If new credentials are provided, create a new API instance
        if client_id and client_secret:
            client_credentials_manager = SpotifyClientCredentials(
                client_id=client_id,
                client_secret=client_secret
            )
            return Spotify(auth_manager=client_credentials_manager)
        
        # Otherwise, use the existing class-level API
        cls.__check_initialized()
        return cls.__api

    @classmethod
    def __lazy(cls, results, api=None):
        """Process paginated results"""
        api = api or cls.__api
        albums = results['items']

        while results['next']:
            results = api.next(results)
            albums.extend(results['items'])

        return results

    @classmethod
    def get_track(cls, ids, client_id=None, client_secret=None):
        """
        Get track information by ID.
        
        Args:
            ids (str): Spotify track ID
            client_id (str, optional): Optional custom Spotify client ID
            client_secret (str, optional): Optional custom Spotify client secret
            
        Returns:
            dict: Track information
        """
        api = cls.__get_api(client_id, client_secret)
        try:
            track_json = api.track(ids)
        except SpotifyException as error:
            if error.http_status in cls.__error_codes:
                raise InvalidLink(ids)

        return track_json

    @classmethod
    def get_tracks(cls, ids: list, market: str = None, client_id=None, client_secret=None):
        """
        Get information for multiple tracks by a list of IDs.
        
        Args:
            ids (list): A list of Spotify track IDs.
            market (str, optional): An ISO 3166-1 alpha-2 country code.
            client_id (str, optional): Optional custom Spotify client ID.
            client_secret (str, optional): Optional custom Spotify client secret.
            
        Returns:
            dict: A dictionary containing a list of track information.
        """
        api = cls.__get_api(client_id, client_secret)
        try:
            tracks_json = api.tracks(ids, market=market)
        except SpotifyException as error:
            if error.http_status in cls.__error_codes:
                # Create a string of the first few IDs for the error message
                ids_preview = ', '.join(ids[:3]) + ('...' if len(ids) > 3 else '')
                raise InvalidLink(f"one or more IDs in the list: [{ids_preview}]")
        
        return tracks_json

    @classmethod
    def get_album(cls, ids, client_id=None, client_secret=None):
        """
        Get album information by ID.
        
        Args:
            ids (str): Spotify album ID
            client_id (str, optional): Optional custom Spotify client ID
            client_secret (str, optional): Optional custom Spotify client secret
            
        Returns:
            dict: Album information
        """
        api = cls.__get_api(client_id, client_secret)
        try:
            album_json = api.album(ids)
        except SpotifyException as error:
            if error.http_status in cls.__error_codes:
                raise InvalidLink(ids)

        tracks = album_json['tracks']
        cls.__lazy(tracks, api)

        return album_json

    @classmethod
    def get_playlist(cls, ids, client_id=None, client_secret=None):
        """
        Get playlist information by ID.
        
        Args:
            ids (str): Spotify playlist ID
            client_id (str, optional): Optional custom Spotify client ID
            client_secret (str, optional): Optional custom Spotify client secret
            
        Returns:
            dict: Playlist information
        """
        api = cls.__get_api(client_id, client_secret)
        try:
            playlist_json = api.playlist(ids)
        except SpotifyException as error:
            if error.http_status in cls.__error_codes:
                raise InvalidLink(ids)

        tracks = playlist_json['tracks']
        cls.__lazy(tracks, api)

        return playlist_json

    @classmethod
    def get_episode(cls, ids, client_id=None, client_secret=None):
        """
        Get episode information by ID.
        
        Args:
            ids (str): Spotify episode ID
            client_id (str, optional): Optional custom Spotify client ID
            client_secret (str, optional): Optional custom Spotify client secret
            
        Returns:
            dict: Episode information
        """
        api = cls.__get_api(client_id, client_secret)
        try:
            episode_json = api.episode(ids)
        except SpotifyException as error:
            if error.http_status in cls.__error_codes:
                raise InvalidLink(ids)

        return episode_json

    @classmethod
    def search(cls, query, search_type='track', limit=10, client_id=None, client_secret=None):
        """
        Search for tracks, albums, artists, or playlists.
        
        Args:
            query (str): Search query
            search_type (str, optional): Type of search ('track', 'album', 'artist', 'playlist')
            limit (int, optional): Maximum number of results to return
            client_id (str, optional): Optional custom Spotify client ID
            client_secret (str, optional): Optional custom Spotify client secret
            
        Returns:
            dict: Search results
        """
        api = cls.__get_api(client_id, client_secret)
        search = api.search(q=query, type=search_type, limit=limit)
        return search

    @classmethod
    def get_artist(cls, ids, client_id=None, client_secret=None):
        """
        Get artist information by ID.
        
        Args:
            ids (str): Spotify artist ID
            client_id (str, optional): Optional custom Spotify client ID
            client_secret (str, optional): Optional custom Spotify client secret
            
        Returns:
            dict: Artist information
        """
        api = cls.__get_api(client_id, client_secret)
        try:
            artist_json = api.artist(ids)
        except SpotifyException as error:
            if error.http_status in cls.__error_codes:
                raise InvalidLink(ids)

        return artist_json

    @classmethod
    def get_artist_discography(cls, ids, album_type='album,single,compilation,appears_on', limit=50, offset=0, client_id=None, client_secret=None):
        """
        Get artist information and discography by ID.
        
        Args:
            ids (str): Spotify artist ID
            album_type (str, optional): Types of albums to include
            limit (int, optional): Maximum number of results
            client_id (str, optional): Optional custom Spotify client ID
            client_secret (str, optional): Optional custom Spotify client secret
            
        Returns:
            dict: Artist discography
        """
        api = cls.__get_api(client_id, client_secret)
        try:
            # Request all types of releases by the artist.
            discography = api.artist_albums(
                ids,
                album_type=album_type,
                limit=limit,
                offset=offset
            )
        except SpotifyException as error:
            if error.http_status in cls.__error_codes:
                raise InvalidLink(ids)
            else:
                raise

        # Ensure that all pages of results are fetched.
        cls.__lazy(discography, api)
        return discography
