import urlMatch from './url.js';
import querystring from 'querystring';
import request from 'superagent';
import { parse } from 'url';
import debuglog from 'debug';

const debug = debuglog('combine.fm:ytmusic');

const standard_body = {'context': {'capabilities': {}, 'client': {'clientName': 'WEB_REMIX', 'clientVersion': '0.1', 'experimentIds': [], 'experimentsToken': '', 'gl': 'DE', 'hl': 'en', 'locationInfo': {'locationPermissionAuthorizationStatus': 'LOCATION_PERMISSION_AUTHORIZATION_STATUS_UNSUPPORTED'}, 'musicAppInfo': {'musicActivityMasterSwitch': 'MUSIC_ACTIVITY_MASTER_SWITCH_INDETERMINATE', 'musicLocationMasterSwitch': 'MUSIC_LOCATION_MASTER_SWITCH_INDETERMINATE', 'pwaInstallabilityStatus': 'PWA_INSTALLABILITY_STATUS_UNKNOWN'}, 'utcOffsetMinutes': 60}, 'request': {'internalExperimentFlags': [{'key': 'force_music_enable_outertube_tastebuilder_browse', 'value': 'true'}, {'key': 'force_music_enable_outertube_playlist_detail_browse', 'value': 'true'}, {'key': 'force_music_enable_outertube_search_suggestions', 'value': 'true'}], 'sessionIndex': {}}, 'user': {'enableSafetyMode': false}}}
const standard_headers = {
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0",
  "Accept": "*/*",
  "Accept-Language": "en-US,en;q=0.5",
  "Content-Type": "application/json",
  "X-Goog-AuthUser": "0",
  "origin": "https://music.youtube.com",
  "X-Goog-Visitor-Id": "CgtWaTB2WWRDeEFUYyjhv-X8BQ%3D%3D"
}
const standard_params = { alt: "json", key: "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30"} // INNERTUBE_API_KEY from music.youtube.com

const base_filter = "Eg-KAQwIA"
const albums_filter = "BAAGAEgACgA"
const tracks_filter = "RAAGAAgACgA"
// If you make a typo, ytmusic searches for a correction. With this filter it will look for the exact match
// since we don't let users type, no sense in letting it autocorrect
const exact_search_filter = "MABqChAEEAMQCRAFEAo%3D"

// The logic here comes from https://github.com/sigma67/ytmusicapi
// If something doesn't work, looking up back there might be a good idea.
export async function search(data, original = {}) {
  let query;
  const various = data.artist.name === 'Various Artists' || data.artist.name === 'Various';
  if (various) {
    data.artist.name = "";
  }
  if (data.type == "track") {
    query = [data.name, data.artist.name, data.albumName]
  } else if (data.type == "album") {
    query = [data.name, data.artist.name]
  } else {
    throw new Error();
  }
  // Add "" to try and make the search better, works for stuff like "The beatles" to reduce noise
  query = query.filter(String).map((entry) => '"' + entry + '"').join(" ")

  let params = base_filter + (data.type == "track" ? tracks_filter : albums_filter) + exact_search_filter
  let request_body = {query, params, ...standard_body }

  const { body } = await request.post("https://music.youtube.com/youtubei/v1/search")
    .set(standard_headers)
    .query(standard_params)
    .send(request_body)

  // no results
  if (body.contents === undefined) {
    debug("Empty body, no results")
    return { service: 'ytmusic' };
  }

  let results;
  if (body.contents.tabbedSearchResultsRenderer !== undefined) {
    results = body.contents.tabbedSearchResultsRenderer.tabs[0].tabRenderer.content
  } else {
    results = body.contents.sectionListRenderer.contents
  }

  // no results
  if (results.length == 1 && results.itemSectionRenderer !== undefined) {
    debug("Only itemSectionRenderer, no results")
    return { service: 'ytmusic' };
  }

  for (const result of results) {
    if (result.musicShelfRenderer === undefined) {
      continue;
    }

    const matches = parse_result_content(result.musicShelfRenderer.contents, data.type)
    // This could probably be done without extra lookups, but it would involve parsing deeply the response.
    // If there's some kind of rate limit on ytmusic's side, this is a good play to start refactoring
    for (const match of matches) {
      const possibleMatch = await lookupId(match, data.type)
      const nameMatch = possibleMatch.name == data.name;
      const artistMatch = data.artist.name == "" ? possibleMatch.artist.name === 'Various Artists' : data.artist.name == possibleMatch.artist.name;
      if (nameMatch && artistMatch) {
        return possibleMatch
      }
    }
  }
  debug("Finished looking up, no results")
  return { service: 'ytmusic' };
}

function parse_result_content(contents, type) {
  let matches = []
  for (const result of contents) {
    const data = result.musicResponsiveListItemRenderer;
    const informed_type = data.flexColumns[1].musicResponsiveListItemFlexColumnRenderer.text.runs[0].text
    if (["Video", "Playlist"].includes(informed_type)) {
      continue;
    }
    let matchId;
    if (type == "track") {
      matchId = data.overlay?.musicItemThumbnailOverlayRenderer.content.musicPlayButtonRenderer.playNavigationEndpoint.watchEndpoint?.videoId
    } else if (type == "album") {
      matchId = data.navigationEndpoint?.browseEndpoint.browseId
    }
    if(matchId) {
      matches.push(matchId)
    }
  }

  return matches
}

async function lookupTrack(id) {
  let request_body = {'video_id': id, ...standard_body }

  const { body } = await request.post("https://music.youtube.com/youtubei/v1/player")
    .set(standard_headers)
    .query(standard_params)
    .send(request_body)
  let song_meta = body.videoDetails

  let description = body.microformat.microformatDataRenderer.description.split(' · ')
  let possible_album_name = description[description.length - 1].split("℗")[0]
  if (!description[description.length - 1].includes("℗")) {
    possible_album_name = "";
  }
  let tags = body.microformat.microformatDataRenderer.tags
  let album_name = ""
  for (const tag of tags) {
    if(possible_album_name.includes(tag)){
      album_name = tag;
    }
  }
  let artists = song_meta.author
  artists = artists.replace(" - Topic", "")

  const artwork = {
    small: song_meta.thumbnail.thumbnails[0].url,
    large: song_meta.thumbnail.thumbnails[song_meta.thumbnail.thumbnails.length-1].url,
  };

  let track_info = {
    service: 'ytmusic',
    type: 'track',
    id: song_meta.videoId,
    name: song_meta.title,
    streamUrl: `https://music.youtube.com/watch?v=${song_meta.videoId}`,
    purchaseUrl: null,
    artwork,
    artist: {
      name: artists,
    },
    album: {
      name: album_name,
    },
  }
  debug(track_info)
  return Promise.resolve(track_info);
}

async function lookupAlbum(id) {
  let request_body = {'browseEndpointContextSupportedConfigs': {'browseEndpointContextMusicConfig': {'pageType': 'MUSIC_PAGE_TYPE_ALBUM'}}, 'browseId': id, ...standard_body }

  const { body } = await request.post("https://music.youtube.com/youtubei/v1/browse")
    .set(standard_headers)
    .query(standard_params)
    .send(request_body)

  let data = body.frameworkUpdates?.entityBatchUpdate.mutations
  if (data === undefined) {
    throw new Error()
  }
  let album_data = data.find((entry) => {
    if (entry.payload.musicAlbumRelease !== undefined) {
      return true
    }
    return false
  }).payload.musicAlbumRelease;
  let artists;
  if (album_data.primaryArtists) {
    artists= data.filter((entry) => {
      if (entry.payload.musicArtist !== undefined) {
        if (album_data.primaryArtists.includes(entry.entityKey)) {
          return true
        }
      }
      return false
    }).map((entry) => entry.payload.musicArtist.name);
  } else { // Various artists, most likely
    artists = [album_data.artistDisplayName];
  }

  const artwork = {
    small: album_data.thumbnailDetails.thumbnails[0].url,
    large: album_data.thumbnailDetails.thumbnails[album_data.thumbnailDetails.thumbnails.length-1].url,
  };
  return Promise.resolve({
    service: 'ytmusic',
    type: 'album',
    id,
    name: album_data.title,
    streamUrl: null,
    streamUrl: `https://music.youtube.com/browse/${id}`,
    purchaseUrl: null,
    artwork,
    artist: {
      name: artists.join(", "),
    },
    playlistId: album_data.audioPlaylistId
  });
}

async function lookupPlaylist(id) {
  let request_body = {enablePersistentPlaylistPanel: true, isAudioOnly: true, playlistId: id, ...standard_body}
  const { body } = await request.post("https://music.youtube.com/youtubei/v1/next")
    .set(standard_headers)
    .query(standard_params)
    .send(request_body)

  // The playlist object is rather complex, but here's what I'm doing: I'll parse the very minimum to get to the first track.
  // At that point, I'm going to check the id of the album in that track. And make a lookup on it. If the album looked up
  // has the same playlist id as the one I'm looking up, it means it's an album and not a playlist and we're good.
  const watchNextRenderer = body.contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer.watchNextTabbedResultsRenderer
  const firstTrack = watchNextRenderer.tabs[0].tabRenderer.content.musicQueueRenderer.content.playlistPanelRenderer.contents[0]
  const runs = firstTrack.playlistPanelVideoRenderer.longBylineText.runs
  const reverse_last_artist_idx = runs.reverse().findIndex((entry) => {
    if (entry.navigationEndpoint === undefined) {
      return false
    }
    return entry.navigationEndpoint.browseEndpoint.browseId.startsWith("UC") ||
      entry.navigationEndpoint.browseEndpoint.browseId.startsWith("FEmusic_library_privately_owned_artist")
  });
  if (reverse_last_artist_idx == -1) {
    debug("Could not find an artist. Implement extra logic from ytmusicapi!");
    throw new Error();
  }
  const last_artist_idx = runs.length - reverse_last_artist_idx - 1;
  if (runs.length - last_artist_idx != 5) {
    debug("No album found, can't find this.");
    throw new Error();
  }
  const albumId = runs[last_artist_idx + 2].navigationEndpoint.browseEndpoint.browseId
  const possibleAlbum = await lookupAlbum(albumId)
  if (possibleAlbum.playlistId = id) {
    return possibleAlbum;
  }
  throw new Error();
}

export async function lookupId(id, type) {
  if (type == 'track') {
    return lookupTrack(id);
  } else if (type == 'album') {
    return lookupAlbum(id);
  } else if (type == 'playlist') {
    return lookupPlaylist(id);
  }
  return { service: 'ytmusic', id };
}

export function parseUrl(url) {
  const parsed = parse(url);
  const query = querystring.parse(parsed.query);
  let id = query.v;
  let list_id = query.list;
  let match;

  if (parsed.path.match(/^\/watch/) && id !== undefined) {
    return lookupId(id, 'track');
  } else if (match = parsed.path.match(/^\/browse\/([A-Za-z0-9_]+)/)) {
    return lookupId(match[1], 'album');
  } else if (match = parsed.path.match(/^\/playlist/) && list_id !== undefined) {
    return lookupId(list_id, 'playlist');
  }
  throw new Error();
}
export const id = 'ytmusic';
export const match = urlMatch;