import download from '../../utils/download'
import fs from 'fs'
import path from 'path'
import music from '../../utils/music'
import { getMusicType } from '../../utils/music/utils'
import { setMeta, saveLrc } from '../../utils'

// state
const state = {
  list: [],
  waitingList: [],
  downloadStatus: {
    RUN: 'run',
    WAITING: 'waiting',
    PAUSE: 'pause',
    ERROR: 'error',
    COMPLETED: 'completed',
  },
}


const dls = {}
const tryNum = {}
let isRuningActionTask = false

const filterFileName = /[\\/:*?#"<>|]/g

// getters
const getters = {
  list: state => state.list || [],
  downloadStatus: state => state.downloadStatus,
}

const checkPath = path => new Promise((resolve, reject) => {
  fs.access(path, fs.constants.F_OK | fs.constants.W_OK, err => {
    if (err) {
      if (err.code === 'ENOENT') {
        fs.mkdir(path, { recursive: true }, err => {
          if (err) return reject(err)
          resolve()
        })
        return
      }
      return reject(err)
    }
    resolve()
  })
})

const getExt = type => {
  switch (type) {
    case '128k':
    case '192k':
    case '320k':
      return 'mp3'
    case 'ape':
      return 'ape'
    case 'flac':
      return 'flac'
    case 'wav':
      return 'wav'
  }
}

const checkList = (list, musicInfo, type, ext) => list.some(s => s.musicInfo.songmid === musicInfo.songmid && (s.type === type || s.ext === ext))

const getStartTask = (list, downloadStatus, maxDownloadNum) => {
  let downloadCount = 0
  const waitList = list.filter(item => item.status == downloadStatus.WAITING ? true : (item.status === downloadStatus.RUN && ++downloadCount && false))
  // console.log(downloadCount, waitList)
  return downloadCount < maxDownloadNum ? waitList.shift() || null : false
}

const awaitRequestAnimationFrame = () => new Promise(resolve => window.requestAnimationFrame(() => resolve()))

const addTasks = async(store, list, type) => {
  if (list.length == 0) return
  let num = 3
  while (num-- > 0) {
    let item = list.shift()
    if (!item) return
    store.dispatch('createDownload', {
      musicInfo: item,
      type: getMusicType(item, type),
    })
  }
  await awaitRequestAnimationFrame()
  await addTasks(store, list, type)
}
const removeTasks = async(store, list) => {
  let num = 20
  while (num-- > 0) {
    let item = list.pop()
    if (!item) return
    let index = store.state.list.indexOf(item)
    if (index < 0) continue
    store.dispatch('removeTask', item)
  }
  await awaitRequestAnimationFrame()
  await removeTasks(store, list)
}

const startTasks = async(store, list) => {
  let num = 5
  while (num-- > 0) {
    let item = list.shift()
    if (!item) return
    if (item.isComplate || item.status == state.downloadStatus.RUN || item.status == state.downloadStatus.WAITING) continue
    let index = store.state.list.indexOf(item)
    if (index < 0) continue
    store.dispatch('startTask', item)
  }
  await awaitRequestAnimationFrame()
  await startTasks(store, list)
}

const pauseTasks = async(store, list, runs = []) => {
  let num = 6
  let index
  let stateList = store.state.list
  while (num-- > 0) {
    let item = list.shift()
    if (item) {
      if (item.isComplate) continue
      switch (item.status) {
        case state.downloadStatus.RUN:
          runs.push(item)
          continue
        case state.downloadStatus.WAITING:
          index = stateList.indexOf(item)
          if (index < 0) return
          store.dispatch('pauseTask', item)
          continue
        default:
          continue
      }
    } else {
      for (const item of runs) {
        index = stateList.indexOf(item)
        if (index < 0) return
        await store.dispatch('pauseTask', item)
      }
      return
    }
  }
  await awaitRequestAnimationFrame()
  await pauseTasks(store, list, runs)
}

const getUrl = (downloadInfo, isRefresh) => {
  const url = downloadInfo.musicInfo.typeUrl[downloadInfo.type]
  if (!downloadInfo.musicInfo._types[downloadInfo.type]) {
    // 兼容旧版酷我源搜索列表过滤128k音质的bug
    if (!(downloadInfo.musicInfo.source == 'kw' && downloadInfo.type == '128k')) return Promise.reject(new Error('该歌曲没有可下载的音频'))

    // return Promise.reject(new Error('该歌曲没有可下载的音频'))
  }
  return url && !isRefresh ? Promise.resolve({ url }) : music[downloadInfo.musicInfo.source].getMusicUrl(downloadInfo.musicInfo, downloadInfo.type).promise
}

/**
 * 设置歌曲meta信息
 * @param {*} downloadInfo
 * @param {*} filePath
 * @param {*} isEmbedPic // 是否嵌入图片
 */
const saveMeta = (downloadInfo, filePath, isEmbedPic, isEmbedLyric) => {
  if (downloadInfo.type === 'ape') return
  const tasks = [
    isEmbedPic
      ? downloadInfo.musicInfo.img
        ? Promise.resolve(downloadInfo.musicInfo.img)
        : music[downloadInfo.musicInfo.source].getPic(downloadInfo.musicInfo).promise.catch(err => {
          console.log(err)
          return null
        })
      : Promise.resolve(),
    isEmbedLyric
      ? downloadInfo.musicInfo.lrc
        ? Promise.resolve(downloadInfo.musicInfo.lrc)
        : music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise.catch(err => {
          console.log(err)
          return null
        })
      : Promise.resolve(),
  ]
  Promise.all(tasks).then(([imgUrl, lyrics]) => {
    setMeta(filePath, {
      title: downloadInfo.musicInfo.name,
      artist: downloadInfo.musicInfo.singer,
      album: downloadInfo.musicInfo.albumName,
      APIC: imgUrl,
      lyrics,
    })
  })
}

/**
 * 保存歌词
 * @param {*} downloadInfo
 * @param {*} filePath
 */
const downloadLyric = (downloadInfo, filePath) => {
  const promise = downloadInfo.musicInfo.lrc
    ? Promise.resolve(downloadInfo.musicInfo.lrc)
    : music[downloadInfo.musicInfo.source].getLyric(downloadInfo.musicInfo).promise
  promise.then(lrc => {
    if (lrc) saveLrc(filePath.replace(/(mp3|flac|ape|wav)$/, 'lrc'), lrc)
  })
}

const refreshUrl = function(commit, downloadInfo) {
  commit('setStatusText', { downloadInfo, text: '链接失效，正在刷新链接' })
  getUrl(downloadInfo, true).then(result => {
    commit('updateUrl', { downloadInfo, url: result.url })
    commit('setStatusText', { downloadInfo, text: '链接刷新成功' })
    const dl = dls[downloadInfo.key]
    if (!dl) return
    dl.refreshUrl(result.url)
    dl.start().catch(err => {
      commit('onError', downloadInfo)
      commit('setStatusText', { downloadInfo, text: err.message })
      this.dispatch('download/startTask')
    })
  }).catch(err => {
    // console.log(err)
    commit('onError', downloadInfo)
    commit('setStatusText', { downloadInfo, text: err.message })
    this.dispatch('download/startTask')
  })
}

/**
 * 删除文件
 * @param {*} path
 */
const deleteFile = path => new Promise((resolve, reject) => {
  fs.access(path, fs.constants.F_OK, err => {
    if (err) return reject(err)
    fs.unlink(path, err => {
      if (err) return reject(err)
      resolve()
    })
  })
})


// actions
const actions = {
  async createDownload({ state, rootState, commit, dispatch }, { musicInfo, type }) {
    let ext = getExt(type)
    if (checkList(state.list, musicInfo, type, ext)) return
    const downloadInfo = {
      isComplate: false,
      status: state.downloadStatus.WAITING,
      statusText: '待下载',
      url: null,
      fileName: `${rootState.setting.download.fileName
        .replace('歌名', musicInfo.name)
        .replace('歌手', musicInfo.singer)}.${ext}`.replace(filterFileName, ''),
      progress: {
        downloaded: 0,
        total: 0,
        progress: 0,
      },
      type,
      ext,
      musicInfo,
      key: `${musicInfo.songmid}${ext}`,
    }
    downloadInfo.filePath = path.join(rootState.setting.download.savePath, downloadInfo.fileName)
    commit('addTask', downloadInfo)
    try { // 删除同路径下的同名文件
      await deleteFile(downloadInfo.filePath)
    } catch (err) {
      if (err.code !== 'ENOENT') return commit('setStatusText', { downloadInfo, text: '文件删除失败' })
    }
    if (dls[downloadInfo.key]) {
      dls[downloadInfo.key].stop().finally(() => {
        delete dls[downloadInfo.key]
        dispatch('startTask', downloadInfo)
      })
    } else {
      // console.log(downloadInfo)
      dispatch('startTask', downloadInfo)
    }
  },
  createDownloadMultiple(store, { list, type }) {
    if (!list.length || isRuningActionTask) return
    isRuningActionTask = true
    return addTasks(store, [...list], type).finally(() => {
      isRuningActionTask = false
    })
  },
  async handleStartTask({ commit, dispatch, rootState }, downloadInfo) {
    // 开始任务
    commit('onStart', downloadInfo)
    commit('setStatusText', { downloadInfo, text: '任务初始化中' })
    try {
      await checkPath(rootState.setting.download.savePath)
    } catch (error) {
      commit('onError', downloadInfo)
      commit('setStatusText', '检查下载目录出错: ' + error.message)
      await dispatch('startTask')
      return
    }
    const _this = this
    const options = {
      url: downloadInfo.url,
      path: rootState.setting.download.savePath,
      fileName: downloadInfo.fileName,
      method: 'get',
      override: true,
      onCompleted() {
        // if (downloadInfo.progress.progress != '100.00') {
        //   delete dls[downloadInfo.key]
        //   return dispatch('startTask', downloadInfo)
        // }
        commit('onCompleted', downloadInfo)
        dispatch('startTask')

        saveMeta(downloadInfo, downloadInfo.filePath, rootState.setting.download.isEmbedPic, rootState.setting.download.isEmbedLyric)
        if (rootState.setting.download.isDownloadLrc) downloadLyric(downloadInfo, downloadInfo.filePath)
        console.log('on complate')
      },
      onError(err) {
        // console.log(tryNum[downloadInfo.key])
        if (++tryNum[downloadInfo.key] > 2) {
          commit('onError', downloadInfo)
          dispatch('startTask')
          return
        }
        if (err.code == 'ENOTFOUND') {
          refreshUrl.call(_this, commit, downloadInfo)
        } else {
          console.log('Download failed, Attempting Retry')
          dls[downloadInfo.key].start()
          commit('setStatusText', { downloadInfo, text: '正在重试' })
        }
      },
      onFail(response) {
        if (++tryNum[downloadInfo.key] > 2) {
          commit('onError', downloadInfo)
          dispatch('startTask')
          return
        }
        switch (response.statusCode) {
          case 401:
          case 403:
          case 410:
            refreshUrl.call(_this, commit, downloadInfo)
        }
      },
      onStart() {
        commit('onStart', downloadInfo)
        console.log('on start')
      },
      onProgress(status) {
        commit('onProgress', { downloadInfo, status })
        console.log(status)
      },
      onStop() {
        commit('pauseTask', downloadInfo)
        dispatch('startTask')
      },
    }
    commit('setStatusText', { downloadInfo, text: '获取URL中...' })
    let p = options.url ? Promise.resolve() : getUrl(downloadInfo).then(result => {
      commit('updateUrl', { downloadInfo, url: result.url })
      if (!result.url) return Promise.reject(new Error('获取URL失败'))
      options.url = result.url
    })
    p.then(() => {
      tryNum[downloadInfo.key] = 0
      dls[downloadInfo.key] = download(options)
    }).catch(err => {
      // console.log(err.message)
      commit('onError', downloadInfo)
      commit('setStatusText', { downloadInfo, text: err.message })
      dispatch('startTask')
    })
  },
  async removeTask({ commit, state, dispatch }, item) {
    if (dls[item.key]) {
      if (item.status == state.downloadStatus.RUN) {
        try {
          await dls[item.key].stop()
        } catch (_) {}
      }
      delete dls[item.key]
    }
    commit('removeTask', item)
    if (item.status != state.downloadStatus.COMPLETED) {
      try {
        await deleteFile(item.filePath)
      } catch (_) {}
    }
    switch (item.status) {
      case state.downloadStatus.RUN:
      case state.downloadStatus.WAITING:
        await dispatch('startTask')
    }
  },
  removeTasks(store, list) {
    let { rootState, state } = store
    if (isRuningActionTask) return
    isRuningActionTask = true
    return removeTasks(store, [...list]).finally(() => {
      let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
      while (result) {
        store.dispatch('startTask', result)
        result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
      }
      isRuningActionTask = false
    })
  },
  async startTask({ state, rootState, commit, dispatch }, downloadInfo) {
    // 检查是否可以开始任务
    let result = getStartTask(state.list, state.downloadStatus, rootState.setting.download.maxDownloadNum)
    if (downloadInfo && !downloadInfo.isComplate && downloadInfo.status != state.downloadStatus.RUN) {
      if (result === false) {
        commit('setStatus', { downloadInfo, status: state.downloadStatus.WAITING })
        return
      }
    } else {
      if (!result) return
      downloadInfo = result
    }

    let dl = dls[downloadInfo.key]
    if (dl) {
      commit('updateFilePath', {
        downloadInfo,
        filePath: path.join(rootState.setting.download.savePath, downloadInfo.fileName),
      })
      dl.updateSaveInfo(rootState.setting.download.savePath, downloadInfo.fileName)
      try {
        await dl.start()
      } catch (error) {
        commit('onError', downloadInfo)
        commit('setStatusText', error.message)
        await dispatch('startTask')
      }
    } else {
      await dispatch('handleStartTask', downloadInfo)
    }
  },
  startTasks(store, list) {
    if (isRuningActionTask) return
    isRuningActionTask = true
    return startTasks(store, [...list]).finally(() => {
      isRuningActionTask = false
    })
  },
  async pauseTask(store, item) {
    if (item.isComplate) return
    let dl = dls[item.key]
    if (dl) {
      try {
        await dl.stop()
      } catch (_) {}
    }
    store.commit('pauseTask', item)
  },
  pauseTasks(store, list) {
    if (isRuningActionTask) return
    isRuningActionTask = true
    return pauseTasks(store, [...list]).finally(() => {
      isRuningActionTask = false
    })
  },
}

// mitations
const mutations = {
  addTask(state, downloadInfo) {
    state.list.unshift(downloadInfo)
  },
  removeTask({ list }, downloadInfo) {
    list.splice(list.indexOf(downloadInfo), 1)
  },
  pauseTask(state, downloadInfo) {
    downloadInfo.status = state.downloadStatus.PAUSE
    downloadInfo.statusText = '暂停下载'
  },
  setStatusText(state, { downloadInfo, index, text }) { // 设置状态文本
    if (downloadInfo) {
      downloadInfo.statusText = text
    } else {
      state.list[index].statusText = text
    }
  },
  setStatus(state, { downloadInfo, index, status }) { // 设置状态及状态文本
    let text
    switch (status) {
      case state.downloadStatus.RUN:
        text = '正在下载'
        break
      case state.downloadStatus.WAITING:
        text = '等待下载'
        break
      case state.downloadStatus.PAUSE:
        text = '暂停下载'
        break
      case state.downloadStatus.ERROR:
        text = '任务出错'
        break
      case state.downloadStatus.COMPLETED:
        text = '下载完成'
        break
    }
    if (downloadInfo) {
      downloadInfo.statusText = text
      downloadInfo.status = status
    } else {
      state.list[index].statusText = text
      state.list[index].status = status
    }
  },
  onCompleted(state, downloadInfo) {
    downloadInfo.isComplate = true
    downloadInfo.status = state.downloadStatus.COMPLETED
    downloadInfo.statusText = '下载完成'
  },
  onError(state, downloadInfo) {
    downloadInfo.status = state.downloadStatus.ERROR
    downloadInfo.statusText = '任务出错'
  },
  onStart(state, downloadInfo) {
    downloadInfo.status = state.downloadStatus.RUN
    downloadInfo.statusText = '正在下载'
  },
  onProgress(state, { downloadInfo, status }) {
    downloadInfo.progress.progress = status.progress
    downloadInfo.progress.downloaded = status.downloaded
    downloadInfo.progress.total = status.total
  },
  setTotal(state, { order, downloadInfo }) {
    downloadInfo.order = order
  },
  updateDownloadList(state, list) {
    state.list = list
  },
  updateUrl(state, { downloadInfo, url }) {
    downloadInfo.url = url
  },
  updateFilePath(state, { downloadInfo, filePath }) {
    if (downloadInfo.filePath === filePath) return
    downloadInfo.filePath = filePath
  },
}

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations,
}
