/* eslint-disable camelcase */
import { AxiosInstance } from 'axios'
import { handleRequestErrs, appendURL, extractIDFromPersonalizedTrackURL } from './util'

import STREAMING_PROTOCOLS from './protocols'
import FORMATS from './formats'

/**
 * A Soundcloud user
 */
export interface User {
  kind: string,
  avatar_url: string,
  city: string,
  comments_count: number,
  country_code: string,
  created_at: string,
  description: string,
  followers_count: number,
  followings_count: number,
  first_name: string,
  full_name: string,
  groups_count: number,
  id: number,
  last_name: string,
  permalink_url: string,
  uri: string,
  username: string
}

/**
 * Details about the track
 */
export interface TrackInfo {
  kind: string
  monetization_model: string,
  id: number,
  policy: string,
  comment_count?: number,
  full_duration?: number,
  downloadable?: false,
  created_at?: string,
  description?: string,
  media?: { transcodings: Transcoding[] },
  title?: string,
  publisher_metadata?: string,
  duration?: number,
  has_downloads_left?: boolean,
  artwork_url?: string,
  public?: boolean,
  streamable?: true,
  tag_list?: string,
  genre?: string,
  reposts_count?: number,
  label_name?: string,
  state?: string,
  last_modified?: string,
  commentable?: boolean,
  uri?: string,
  download_count?: number,
  likes_count?: number,
  display_date?: string,
  user_id?: number,
  waveform_url?: string,
  permalink?: string,
  permalink_url?: string,
  user?: User,
  playback_count?: number
}

/**
 * Details about a Set
 */
export interface SetInfo {
  duration: number,
  permalink_url: string,
  reposts_count: number,
  genre: string,
  permalink: string,
  purchase_url?: string,
  description?: string,
  uri: string,
  label_name?: string,
  tag_list: string,
  set_type: string,
  public: boolean,
  track_count: number,
  user_id: number,
  last_modified: string,
  license: string,
  tracks: TrackInfo[],
  id: number,
  release_date?: string,
  display_date: string,
  sharing: string,
  secret_token?: string,
  created_at: string,
  likes_count: number,
  kind: string,
  purchase_title?: string,
  managed_by_feeds: boolean,
  artwork_url?: string,
  is_album: boolean,
  user: User,
  published_at: string,
  embeddable_by: string
}

/**
 * Represents an audio link to a Soundcloud Track
 */
export interface Transcoding {
  url: string,
  preset: string,
  snipped: boolean,
  format: { protocol: STREAMING_PROTOCOLS, mime_type: FORMATS }
}

const getTrackInfoBase = async (clientID: string, axiosRef: AxiosInstance, ids: number[], playlistID?: number, playlistSecretToken?: string): Promise<TrackInfo[]> => {
  let url = appendURL('https://api-v2.soundcloud.com/tracks', 'ids', ids.join(','), 'client_id', clientID)
  if (playlistID && playlistSecretToken) {
    url = appendURL(url, 'playlistId', '' + playlistID, 'playlistSecretToken', playlistSecretToken)
  }
  try {
    const { data } = await axiosRef.get(url)

    return data as TrackInfo[]
  } catch (err) {
    throw handleRequestErrs(err)
  }
}

/** @internal */
export const getInfoBase = async <T extends TrackInfo | SetInfo>(url: string, clientID: string, axiosRef: AxiosInstance): Promise<T> => {
  try {
    const res = await axiosRef.get(appendURL('https://api-v2.soundcloud.com/resolve', 'url', url, 'client_id', clientID), {
      withCredentials: true
    })

    return res.data as T
  } catch (err) {
    throw handleRequestErrs(err)
  }
}

/** @internal */
const getSetInfoBase = async (url: string, clientID: string, axiosRef: AxiosInstance): Promise<SetInfo> => {
  const setInfo = await getInfoBase<SetInfo>(url, clientID, axiosRef)
  const temp = [...setInfo.tracks].map(track => track.id)
  const playlistID = setInfo.id
  const playlistSecretToken = setInfo.secret_token
  const incompleteTracks = setInfo.tracks.filter(track => !track.title)
  if (incompleteTracks.length === 0) {
    return setInfo
  }
  const completeTracks = setInfo.tracks.filter(track => track.title)

  const ids = incompleteTracks.map(t => t.id)
  if (ids.length > 50) {
    const splitIds = []
    for (let x = 0; x <= Math.floor(ids.length / 50); x++) {
      splitIds.push([])
    }

    for (let x = 0; x < ids.length; x++) {
      const i = Math.floor(x / 50)
      splitIds[i].push(ids[x])
    }

    const promises = splitIds.map(async ids => await getTrackInfoByID(clientID, axiosRef, ids, playlistID, playlistSecretToken))
    const info = await Promise.all(promises)
    setInfo.tracks = completeTracks.concat(...info)
    setInfo.tracks = sortTracks(setInfo.tracks, temp)
    return setInfo
  }
  const info = await getTrackInfoByID(clientID, axiosRef, ids, playlistID, playlistSecretToken)

  setInfo.tracks = completeTracks.concat(info)
  setInfo.tracks = sortTracks(setInfo.tracks, temp)
  return setInfo
}

/** @internal */
const sortTracks = (tracks: TrackInfo[], ids: number[]): TrackInfo[] => {
  for (let i = 0; i < ids.length; i++) {
    if (tracks[i].id !== ids[i]) {
      for (let j = 0; j < tracks.length; j++) {
        if (tracks[j].id === ids[i]) {
          const temp = tracks[i]
          tracks[i] = tracks[j]
          tracks[j] = temp
        }
      }
    }
  }

  return tracks
}

/** @internal */
const getInfo = async (url: string, clientID: string, axiosInstance: AxiosInstance): Promise<TrackInfo> => {
  let data
  if (url.includes('https://soundcloud.com/discover/sets/personalized-tracks::')) {
    const idString = extractIDFromPersonalizedTrackURL(url)
    if (!idString) throw new Error('Could not parse track ID from given URL: ' + url)
    let id: number
    try {
      id = parseInt(idString)
    } catch (err) {
      throw new Error('Could not parse track ID from given URL: ' + url)
    }

    data = (await getTrackInfoByID(clientID, axiosInstance, [id]))[0]
    if (!data) throw new Error('Could not find track with ID: ' + id)
  } else {
    data = await getInfoBase<TrackInfo>(url, clientID, axiosInstance)
  }
  if (!data.media) throw new Error('The given URL does not link to a Soundcloud track')
  return data
}

/** @internal */
export const getSetInfo = async (url: string, clientID: string, axiosInstance: AxiosInstance): Promise<SetInfo> => {
  const data = await getSetInfoBase(url, clientID, axiosInstance)
  if (!data.tracks) throw new Error('The given URL does not link to a Soundcloud set')
  return data
}

/** @intenral */
export const getTrackInfoByID = async (clientID: string, axiosInstance: AxiosInstance, ids: number[], playlistID?: number, playlistSecretToken?: string): Promise<TrackInfo[]> => {
  return await getTrackInfoBase(clientID, axiosInstance, ids, playlistID, playlistSecretToken)
}
export default getInfo
