import requests
import urllib.parse
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from PIL import Image
from io import BytesIO
from tqdm import tqdm

from src.backends.spotify.SpotifyAuthenticator import authenticate

from src.helpers import *

FETCH_STEP = 50
token = ''
REQUEST_TIMEOUT = 30


def _build_session():
    """Session that retries transient failures (dropped connections, 5xx) and
    honours Spotify's 429 Retry-After, so a single reset mid-fetch doesn't
    abort the whole run."""
    session = requests.Session()
    retry = Retry(
        total=6,
        connect=6,
        read=6,
        backoff_factor=1.5,  # sleeps ~0, 1.5, 3, 6, 12, 24s between attempts
        status_forcelist=(429, 500, 502, 503, 504),
        allowed_methods=frozenset({"GET", "POST"}),
        respect_retry_after_header=True,
        raise_on_status=False,
    )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount("https://", adapter)
    session.mount("http://", adapter)
    return session


_session = _build_session()


def fetch(endpoint, method='GET', body=None):
    url = endpoint if endpoint.startswith('http') else f'https://api.spotify.com/{endpoint}'
    headers = {
        'Authorization': f'Bearer {token}',
    }

    response = _session.request(method, url, headers=headers, json=body, timeout=REQUEST_TIMEOUT)
    response.raise_for_status()
    return response.json()


def fetch_paginated(endpoint, desc="Fetching", leave=True):
    """Collect every item of a paging object by following its `next` field,
    which is Spotify's recommended way to page (more robust than tracking
    offset/total manually). Shows a tqdm progress bar driven by the paging
    object's `total`."""
    items = []
    page = fetch(endpoint)
    with tqdm(total=page.get('total'), desc=desc, unit="item", leave=leave) as pbar:
        while True:
            batch = page.get('items', [])
            items += batch
            pbar.update(len(batch))
            next_url = page.get('next')
            if not next_url:
                break
            page = fetch(next_url)
    return items


def find_song(song_pattern):
    query = urllib.parse.quote(song_pattern)
    return fetch(f"v1/search?type=track&limit=10&q={query}")['tracks']['items']


def fetch_playlists(user_id):
    return fetch_paginated(f"v1/me/playlists?limit={FETCH_STEP}", desc="Fetching playlists")


def fetch_playlist_items(playlist_id):
    return fetch_paginated(
        f"v1/playlists/{playlist_id}/tracks?limit={FETCH_STEP}",
        desc="Fetching playlist tracks",
        leave=False,
    )


def fetch_tracks(user_id):
    tracks = fetch_paginated(f"v1/me/tracks?limit={FETCH_STEP}&market=ES", desc="Fetching liked tracks")
    save_data(tracks, "tracks")
    return tracks


def fetch_tracks_top():
    tracks = fetch_paginated(f"v1/me/top/tracks?limit={FETCH_STEP}&time_range=long_term", desc="Fetching top tracks")
    save_data(tracks, "top_tracks")
    return tracks


def fetch_artists_top():
    artists = fetch_paginated(f"v1/me/top/artists?limit={FETCH_STEP}&time_range=long_term", desc="Fetching top artists")
    save_data(artists, "top_artists")
    return artists


def fetch_all_playlist_data(user_id):
    playlists = fetch_playlists(user_id)

    save_data(playlists, 'playlists')

    print(len(playlists))

    playlist_items = {}

    print("Fetching playlists items")

    index = 0
    for pl in playlists:
        pl_id = pl['id']
        name = pl['name']
        playlist_items[name] = fetch_playlist_items(pl_id)
        print(f"{index} - {len(playlist_items[name])}")
        index += 1

    save_data(playlist_items, "playlistTracks")


def get_user_data():
    print("Fetching user data")

    user_profile = fetch(f'v1/me')
    user_id = user_profile['id']
    user_data = fetch(f'v1/users/{user_id}')

    save_data(user_profile, "user_profile")
    save_data(user_data, "user_data")
    return user_id


def save_image_from_url(url, name, image_dir="playlist_covers"):
    response = requests.get(url)

    if response.status_code != 200:
        raise RuntimeError("Cannot fetch the playlist cover")

    image = Image.open(BytesIO(response.content))

    directory = os.path.join(get_workdir(), image_dir)
    os.makedirs(directory, exist_ok=True)
    file_path = os.path.join(directory, f"{name}.jpg")
    print(f"Image saved {file_path}")
    image.save(file_path)


def load_playlist_covers():
    playlists = get_data('playlists')

    for pl in playlists:
        if len(pl['images']):
            url = pl['images'][0]['url']
            save_image_from_url(url, pl['name'])


def load_user_cover():
    user_data = get_data("user_data")
    url = user_data['images'][1]['url']
    save_image_from_url(url, "user", ".")


def load_artworks():
    load_user_cover()
    load_playlist_covers()


def update_access_token(id, secret):
    global token
    token = authenticate(id, secret)


def fetch_data():
    user_id = get_user_data()

    fetch_tracks(user_id)
    fetch_all_playlist_data(user_id)
    fetch_tracks_top()
    fetch_artists_top()

    load_artworks()

def fetch_all(id, secret, workdir):
    set_workdir(workdir / "spotify")
    update_access_token(id, secret)
    fetch_data()

if __name__ == "__main__":
    update_access_token("", "")
    fetch_data()
