import { ApiResponse, ApisauceInstance, create } from 'apisauce'
import Cookies from 'js-cookie'

import { getGeneralApiProblem } from 'services/api/apiProblem'
import { KOMI_SPOTIFY_ACCESS_TOKEN } from 'services/SpotifyService'

import { ApiConfig, DEFAULT_SPOTIFY_API_CONFIG } from './api.config'

import { DspType } from 'types/dsp'

export enum SpotifySearchType {
  ALBUM = 'album',
  TRACK = 'track'
}

export enum SpotifyPayloadType {
  ALBUM = 'album',
  ARTIST = 'artist',
  TRACK = 'track'
}

export interface SpotifyImagePayload {
  height: number
  url: string
  width: number
}

export interface SpotifyArtistPayload {
  external_urls: Record<string, string>
  href: string
  id: string
  name: string
  type: SpotifyPayloadType.ARTIST
  uri: string
}

export enum SpotifyAlbumPayloadType {
  ALBUM = 'album',
  SINGLE = 'single'
}

export interface SpotifyPayload {
  type: SpotifyPayloadType
}

/*
 * Payload partials
 */

export interface SpotifyCopyright {
  text: string
  type: string
}

export interface SpotifyImage {
  height: number
  width: number
  url: string
}

export interface SpotifyArtistBrief {
  external_urls: Record<string, string>
  href: string
  id: string
  name: string
  type: string
  uri: string
}

export interface SpotifyAlbumTracks {
  href: string
  items: SpotifyTrackBrief[]
  limit: number
  next?: any
  offset: number
  previous?: any
  total: number
}

/*
 * Albums
 */

export interface SpotifyAlbumBrief extends SpotifyPayload {
  album_type: string
  artists: SpotifyArtistBrief[]
  external_urls: Record<string, string>
  href: string
  id: string
  images: SpotifyImage[]
  name: string
  release_date: string
  release_date_precision: string
  total_tracks: number
  type: SpotifyPayloadType.ALBUM
  uri: string
}

export interface SpotifyAlbumSearchPayload extends SpotifyAlbumBrief {
  album_type: SpotifyAlbumPayloadType
  artists: SpotifyArtistBrief[]
  available_markets: string[]
  external_urls: Record<DspType, string>
  href: string
  id: string
  images: SpotifyImagePayload[]
  name: string
  release_date: string
  release_date_precision: string
  total_tracks: number
  uri: string
}

export interface SpotifyAlbum extends SpotifyAlbumSearchPayload {
  copyrights: SpotifyCopyright[]
  external_ids: Record<string, string>
  genres: any[]
  label: string
  popularity: number
}

/*
 * Tracks
 */

export interface SpotifyTrackBrief extends SpotifyPayload {
  album: SpotifyAlbumBrief
  artists: SpotifyArtistBrief[]
  disc_number: number
  duration_ms: number
  explicit: boolean
  external_urls: Record<string, string>
  external_ids: Record<string, string>
  href: string
  id: string
  is_local: boolean
  is_playable: boolean
  name: string
  popularity: number
  preview_url: string
  track_number: number
  type: SpotifyPayloadType.TRACK
  uri: string
}

export interface SpotifyTrackSearchPayload extends SpotifyTrackBrief {
  album: SpotifyAlbumSearchPayload
  available_markets: string[]
}

export interface SpotifyTrack extends SpotifyTrackBrief {
  album: SpotifyAlbumBrief
}

/*
 * Search
 */

export interface SpotifySearchPayload {
  albums: {
    items: SpotifyAlbumSearchPayload[]
  }
  tracks: {
    items: SpotifyTrackSearchPayload[]
  }
}

export interface SpotifySearchItems {
  albums: SpotifyAlbumSearchPayload[]
  tracks: SpotifyTrackSearchPayload[]
}

export class SpotifyApi {
  api: ApisauceInstance

  constructor(config: ApiConfig = DEFAULT_SPOTIFY_API_CONFIG) {
    this.api = create({
      baseURL: config.url,
      timeout: config.timeout
    })

    this.api.axiosInstance.interceptors.request.use(
      async config => {
        const headers: Record<string, string> = {}

        const spotifyAccessToken = Cookies.get(KOMI_SPOTIFY_ACCESS_TOKEN)
        if (spotifyAccessToken) {
          headers['Authorization'] = `Bearer ${spotifyAccessToken}`
        }

        config.headers = { ...config.headers, ...headers }

        return config
      },
      error => {
        // Do something with request error
        return Promise.reject(error)
      }
    )
  }

  async getSpotifyById(type: string, id: string): Promise<any> {
    // this url will hack cache from spotify.
    const response = await this.api.get(`/${type}/${id}?komi=${Date.now()}`)
    if (!response.ok) {
      return getGeneralApiProblem(response)
    }
    return { ok: response.ok, response: response.data }
  }

  async getArtistById(id: string): Promise<any> {
    const response = await this.api.get(`/artists/${id}`)

    if (!response.ok) {
      return getGeneralApiProblem(response)
    }

    return response.data
  }

  async getTrackById(id: string): Promise<SpotifyTrack> {
    const response = await this.api.get<SpotifyTrack>(`/tracks/${id}`)

    if (!response.ok) {
      throw this.createError(response)
    }

    if (!response.data) {
      throw new Error(`No data returned for track ${id}`)
    }

    return response.data
  }

  async getAlbumById(id: string): Promise<SpotifyAlbum> {
    const response = await this.api.get<SpotifyAlbum>(`/albums/${id}`)

    if (!response.ok) {
      throw this.createError(response)
    }

    if (!response.data) {
      throw new Error(`No data returned for album ${id}`)
    }

    return response.data
  }

  async search(
    searchTerm: string,
    searchTypes: SpotifySearchType[] = [
      SpotifySearchType.ALBUM,
      SpotifySearchType.TRACK
    ]
  ): Promise<SpotifySearchItems> {
    const searchTypeQuery = encodeURIComponent(searchTypes.join(','))

    const response = await this.api.get<SpotifySearchPayload>(
      `/search/?q=${searchTerm}&type=${searchTypeQuery}`
    )

    if (!response.ok) {
      throw this.createError(response)
    }

    if (!response.data) {
      throw new Error(`No data returned for search term ${searchTerm}`)
    }

    const albums = response.data.albums.items
    const tracks = response.data.tracks.items

    return { albums, tracks }
  }

  createError<T>(response: ApiResponse<T>) {
    const error = getGeneralApiProblem(response)

    const messageParts = ['An API error occured']
    if (error !== null) {
      messageParts.push(`of kind ${error.kind}`)
    }

    return new Error(messageParts.join(' '))
  }
}

export default new SpotifyApi()
