package com.sedmelluq.discord.lavaplayer.source.youtube;

import com.sedmelluq.discord.lavaplayer.tools.FriendlyException;
import com.sedmelluq.discord.lavaplayer.tools.JsonBrowser;
import com.sedmelluq.discord.lavaplayer.tools.Units;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpClientTools;
import com.sedmelluq.discord.lavaplayer.tools.io.HttpInterface;
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo;
import com.sedmelluq.discord.lavaplayer.track.BasicAudioPlaylist;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;

import static com.sedmelluq.discord.lavaplayer.tools.FriendlyException.Severity.SUSPICIOUS;

/**
 * Handles loading of YouTube mixes.
 */
public class YoutubeMixProvider implements YoutubeMixLoader {
  /**
   * Loads tracks from mix in parallel into a playlist entry.
   *
   * @param mixId ID of the mix
   * @param selectedVideoId Selected track, {@link AudioPlaylist#getSelectedTrack()} will return this.
   * @return Playlist of the tracks in the mix.
   */
  public AudioPlaylist load(
      HttpInterface httpInterface,
      String mixId,
      String selectedVideoId,
      Function<AudioTrackInfo, AudioTrack> trackFactory
  ) {
    String playlistTitle = "YouTube mix";
    List<AudioTrack> tracks = new ArrayList<>();

    String mixUrl = "https://www.youtube.com/watch?v=" + selectedVideoId + "&list=" + mixId + "&pbj=1";

    try (CloseableHttpResponse response = httpInterface.execute(new HttpGet(mixUrl))) {
      int statusCode = response.getStatusLine().getStatusCode();
      if (!HttpClientTools.isSuccessWithContent(statusCode)) {
        throw new IOException("Invalid status code for mix response: " + statusCode);
      }

      JsonBrowser body = JsonBrowser.parse(response.getEntity().getContent());
      JsonBrowser playlist = body.index(3).get("response")
              .get("contents")
              .get("twoColumnWatchNextResults")
              .get("playlist")
              .get("playlist");

      JsonBrowser title = playlist.get("title");

      if (!title.isNull()) {
        playlistTitle = title.text();
      }

      extractPlaylistTracks(playlist.get("contents"), tracks, trackFactory);
    } catch (IOException e) {
      throw new FriendlyException("Could not read mix page.", SUSPICIOUS, e);
    }

    if (tracks.isEmpty()) {
      throw new FriendlyException("Could not find tracks from mix.", SUSPICIOUS, null);
    }

    AudioTrack selectedTrack = findSelectedTrack(tracks, selectedVideoId);
    return new BasicAudioPlaylist(playlistTitle, tracks, selectedTrack, false);
  }

  private void extractPlaylistTracks(
      JsonBrowser browser,
      List<AudioTrack> tracks,
      Function<AudioTrackInfo, AudioTrack> trackFactory
  ) {
    for (JsonBrowser video : browser.values()) {
      JsonBrowser renderer = video.get("playlistPanelVideoRenderer");
      String title = renderer.get("title").get("simpleText").text();
      String author = renderer.get("longBylineText").get("runs").index(0).get("text").text();
      String durationStr = renderer.get("lengthText").get("simpleText").text();
      long duration = parseDuration(durationStr);
      String identifier = renderer.get("videoId").text();
      String uri = "https://youtube.com/watch?v=" + identifier;

      AudioTrackInfo trackInfo = new AudioTrackInfo(title, author, duration, identifier, false, uri);
      tracks.add(trackFactory.apply(trackInfo));
    }
  }

  private long parseDuration(String duration) {
    String[] parts = duration.split(":");

    if (parts.length == 3) { // hh::mm:ss
      int hours = Integer.parseInt(parts[0]);
      int minutes = Integer.parseInt(parts[1]);
      int seconds = Integer.parseInt(parts[2]);
      return (hours * 3600000) + (minutes * 60000) + (seconds * 1000);
    } else if (parts.length == 2) { // mm:ss
      int minutes = Integer.parseInt(parts[0]);
      int seconds = Integer.parseInt(parts[1]);
      return (minutes * 60000) + (seconds * 1000);
    } else {
      return Units.DURATION_MS_UNKNOWN;
    }
  }

  private AudioTrack findSelectedTrack(List<AudioTrack> tracks, String selectedVideoId) {
    if (selectedVideoId != null) {
      for (AudioTrack track : tracks) {
        if (selectedVideoId.equals(track.getIdentifier())) {
          return track;
        }
      }
    }

    return null;
  }
}