import React, { Dispatch, SetStateAction, useState } from 'react'
import { FormInstance, Form } from 'antd'

import { SmartLinkSearchEntry } from './SmartLinkSearchEntry'
import { Select, Option } from 'components/Select'

import { ReactComponent as CloseIcon } from 'icons/close-outline.svg'

import { SearchResult } from './types'
import { DspResource, DspResourceType } from 'types/dsp'
import { Nullable } from 'types/utilities'

import spotifyApi, {
  SpotifyAlbumBrief,
  SpotifyTrackBrief,
  SpotifyImage
} from 'services/api/spotify.api'
import spotifyUri, { Album, Track } from 'spotify-uri'

import { debounceAsync as debounce } from 'utils/rateLimiting'
import { to } from 'utils/to'
import { noop } from 'utils/noop'
import { SmartLinkSearchResult } from '../SmartLinkModal'

const getBestThumbnail = (images: SpotifyImage[]): SpotifyImage =>
  images.reduce((max, image) => (max.width > image.width ? max : image))

const mapAlbumToSearchResult = (album: SpotifyAlbumBrief): SearchResult => {
  const { name, images, artists, total_tracks, id, release_date } = album
  const releaseYear = new Date(release_date).getFullYear()

  const { url: imageUrl } = getBestThumbnail(images)

  const artistNames = artists.map(artist => artist.name)

  const resourceIds = {
    [DspResourceType.ALBUM]: id
  }

  return {
    type: DspResourceType.ALBUM,
    name,
    artistNames,
    length: total_tracks,
    imageUrl,
    releaseYear,
    resourceIds,
    externalIds: {}
  }
}
const mapTrackToSearchResult = (track: SpotifyTrackBrief): SearchResult => {
  const {
    album,
    artists,
    duration_ms,
    external_ids: externalIds,
    id: trackId,
    name
  } = track

  const { id: albumId, images, release_date } = album
  const releaseYear = new Date(release_date).getFullYear()

  const { url: imageUrl } = getBestThumbnail(images)

  const artistNames = artists.map(artist => artist.name)

  const resourceIds = {
    [DspResourceType.ALBUM]: albumId,
    [DspResourceType.TRACK]: trackId
  }

  return {
    type: DspResourceType.TRACK,
    name,
    artistNames,
    length: Math.round(duration_ms / 1000),
    imageUrl,
    releaseYear,
    resourceIds,
    externalIds
  }
}
const getSpotifyResourceBySearchTerm = async (value: string) => {
  const normalizedValue = value.trim()
  const spotifyResourceUrlRegex = new RegExp(
    '^https?://[a-zA-Z]*.spotify.com/?'
  )

  if (spotifyResourceUrlRegex.test(normalizedValue)) {
    return getSpotifyResourceByUri(normalizedValue)
  } else {
    const searchResults = await spotifyApi.search(
      standardizeSearchValue(normalizedValue)
    )
    const { albums, tracks } = searchResults

    const albumResources = albums.map(mapAlbumToSearchResult)
    const trackResources = tracks.map(mapTrackToSearchResult)

    return [...trackResources, ...albumResources]
  }
}

const getSpotifyResourceByUri = async (
  uri: string
): Promise<SearchResult[]> => {
  const parsedUri = spotifyUri.parse(uri)

  switch (parsedUri.type) {
    case 'track':
      const trackUri = parsedUri as Track
      const track = await spotifyApi.getTrackById(trackUri.id)

      return [mapTrackToSearchResult(track)]

    case 'album':
      const albumUri = parsedUri as Album
      const album = await spotifyApi.getAlbumById(albumUri.id)

      return [mapAlbumToSearchResult(album)]
  }

  throw new Error(`Unhandled Spotify resource URI type ${parsedUri.type}`)
}
const standardizeSearchValue = (value: string) => {
  // https://www.regextester.com/104595
  const isrcRegex = /^[A-Z]{2}-?\w{3}-?\d{2}-?\d{5}$/
  const upcRegex = /^[0-9]{11,13}$/

  if (isrcRegex.test(value)) {
    return `isrc:${value}`
  }
  if (upcRegex.test(value)) {
    return `upc:${value}`
  }

  return value
}

const debouncedSpotifyResourceSearch = debounce(
  getSpotifyResourceBySearchTerm,
  300
)

interface SmartLinkSearchInputProps {
  form?: FormInstance
  handleSelect?: (searchResult: DspResource) => void
  searchResult: Nullable<SmartLinkSearchResult>
  setSearchResult: Dispatch<SetStateAction<Nullable<SmartLinkSearchResult>>>
  setSelectedResource: Dispatch<SetStateAction<Nullable<DspResource>>>
}

export const SmartLinkSearchInput: React.FC<SmartLinkSearchInputProps> = ({
  form,
  handleSelect = noop,
  searchResult,
  setSearchResult,
  setSelectedResource
}) => {
  const [searchResults, setSearchResults] = useState<SearchResult[]>([])

  const handleResourceSelect = async (resultIndex: number) => {
    // For the selection, get the album
    const searchResult = searchResults[resultIndex]
    const albumId = searchResult.resourceIds[DspResourceType.ALBUM]

    if (!albumId) {
      // TODO: What should we do in this edge case?
      return
    }

    // Get the album UPC, which is not present in the search payload
    const resourceAlbum = await spotifyApi.getAlbumById(albumId)
    const {
      external_ids: { upc }
    } = resourceAlbum

    // Get the ISRC - if it doesn't exist, this will be undefined
    const isrc = searchResult.externalIds.isrc

    //Get available markets
    const location = resourceAlbum.available_markets[0]
    const resource: DspResource = {
      name: searchResult.name,
      albumName: resourceAlbum.name,
      type: searchResult.type,
      upc,
      isrc,
      location,
      artists: resourceAlbum.artists[0].name
    }

    setSearchResult(searchResult)

    setSelectedResource(resource)
    handleSelect(resource)
  }

  const handleResourceSearch = async (value: string) => {
    if (!value) {
      return
    }

    // The search results do not contain album UPCs, even for album results.
    // These will be fetched for the selected item, in the appropriate event handler
    const [searchError, searchResults] = await to(
      debouncedSpotifyResourceSearch(value)
    )
    if (searchError) {
      return
    }

    // Cast required for current TypeScript version?
    setSearchResults(searchResults as SearchResult[])
  }

  const clearResult = () => {
    form?.setFieldsValue({ searchResult: undefined })

    setSelectedResource(null)
    setSearchResult(null)
    setSearchResults([])
  }

  const handleKeyUp: React.KeyboardEventHandler = event => {
    if (event.key === 'Backspace') {
      clearResult()
    }
  }

  return (
    <Form.Item
      label="Search"
      labelAlign="left"
      name="searchResult"
      valuePropName="searchResult"
      rules={[{ required: true, message: 'Please select a search result' }]}
      colon={false}
    >
      <Select
        className="smart-link--search-input"
        showSearch
        placeholder="Enter link, artist + track/album, UPC or ISRC"
        optionFilterProp="children"
        value={
          searchResult ? (
            <SmartLinkSearchEntry
              result={searchResult}
              suffixIcon={
                <CloseIcon
                  data-testid="smart-link--search-clear"
                  role="button"
                  onClick={clearResult}
                  className="smart-link--search-clear"
                />
              }
            />
          ) : null
        }
        onKeyUp={handleKeyUp}
        onSearch={handleResourceSearch}
        onSelect={handleResourceSelect}
        filterOption={false}
        notFoundContent={null}
        showArrow={false}
        getPopupContainer={trigger => trigger.parentElement || document.body}
      >
        {searchResults.map((result, index) => (
          <Option key={index} value={index}>
            <SmartLinkSearchEntry result={result} />
          </Option>
        ))}
      </Select>
    </Form.Item>
  )
}
