import axios from 'axios'
import { APIError, callOrReturn, handleApiError } from '../api-helper'
import { getISODateString, has } from '@/utilities'
import { useAuthStore } from '@/stores/auth'
import { createSteeringURL } from '@/api'

const cloneMinimalShowObject = function (show) {
  /* returns a new minimal object from the current show object with all
    properties needed for a PUT request to the /show/ endpoint */
  const s = {}
  s.name = show.name
  s.slug = show.slug
  s.short_description = show.short_description
  s.fundingcategory = show.fundingcategory
  s.type = show.type
  // we do not want the arrays do be passed as references, because the
  // current show object should not get modified when the update object
  // gets modified, therefore we use slice to clone the arrays
  s.category = show.category.slice()
  s.hosts = show.hosts.slice()
  s.owners = show.owners.slice()
  s.language = show.language.slice()
  s.topic = show.topic.slice()
  s.musicfocus = show.musicfocus.slice()
  return s
}

const state = {
  shows: [],
  schedule: null,
  schedules: [],
  scheduleTimeslots: [],
  timeslots: [],
  notes: [],
  types: [],
  fundingcategories: [],
  categories: [],
  topics: [],
  musicfocus: [],
  languages: [],
  hosts: [],
  loaded: {
    shows: false,
    timeslots: false,
    notes: false,
    schedule: false,
    scheduleTimeslots: false,
    schedules: false,
    types: false,
    fundingcategories: false,
    categories: false,
    topics: false,
    musicfocus: false,
    languages: false,
    hosts: false,
  },
  selected: {
    index: 0, // index of the currently selected show in our shows array
    id: 0, // actual id of the currently selected show
  },
}

const getters = {
  shows: (state) => state.shows,
  selectedShow: (state) => state.shows[state.selected.index],
  schedule: (state) => state.schedule,
  schedules: (state) => state.schedules,
  scheduleTimeslots: (state) => state.scheduleTimeslots,
  timeslots: (state) => state.timeslots,
  notes: (state) => state.notes,
  types: (state) => state.types,
  fundingcategories: (state) => state.fundingcategories,
  categories: (state) => state.categories,
  topics: (state) => state.topics,
  musicfocus: (state) => state.musicfocus,
  languages: (state) => state.languages,
  hosts: (state) => state.hosts,
  getShowByDataParam: (state) => (data) => {
    let show
    if (data.id !== undefined) {
      show = state.shows.find((s) => s.id === data.id)
      if (show === undefined) {
        console.error('getShowByDataParam: ID not found in store!')
      }
    } else if (data.index !== undefined) {
      show = state.shows[data.index]
    } else {
      console.error('getShowByDataParam: no ID or index was provided')
    }
    return show
  },
  getTimeslotById: (state) => (id) => {
    return state.timeslots.find((s) => s.id === id)
  },
}

const mutations = {
  loading(state, item) {
    state.loaded[item] = false
  },
  finishLoading(state, item) {
    state.loaded[item] = true
  },

  setShows(state, shows) {
    state.shows = shows
  },
  addShow(state, show) {
    state.shows.push(show)
    state.shows.sort((a, b) => a.name.toLowerCase() > b.name.toLowerCase())
  },

  setSchedule(state, schedule) {
    state.schedule = schedule
  },
  setSchedules(state, schedules) {
    state.schedules = schedules
  },
  setScheduleTimeslots(state, slots) {
    state.scheduleTimeslots = slots
  },

  setTimeslots(state, slots) {
    state.timeslots = slots
  },
  setTimeslot(state, slot) {
    const index = state.timeslots.findIndex((s) => s.id === slot.id)
    state.timeslots.splice(index, 1, slot)
  },

  setNotes(state, notes) {
    state.notes = notes
  },
  addNote(state, note) {
    state.notes.push(note)
  },
  setNote(state, note) {
    const index = state.notes.findIndex((n) => n.id === note.id)
    state.notes.splice(index, 1, note)
  },

  setName(state, data) {
    const index = state.shows.findIndex((s) => s.id === data.id)
    state.shows[index].name = data.text
  },
  setShortDescription(state, data) {
    const index = state.shows.findIndex((s) => s.id === data.id)
    state.shows[index].short_description = data.text
  },
  setDescription(state, data) {
    const index = state.shows.findIndex((s) => s.id === data.id)
    state.shows[index].description = data.text
  },
  setActive(state, data) {
    const index = state.shows.findIndex((s) => s.id === data.id)
    state.shows[index].is_active = data.active
  },
  setProperty(state, data) {
    const index = state.shows.findIndex((s) => s.id === data.id)
    state.shows[index][data.property] = data.value
  },

  setMetaArray(state, data) {
    state[data.property] = data.value
  },

  switchShow(state, index) {
    if (state.loaded.shows) {
      state.selected.index = index
      state.selected.id = state.shows[index].id
    }
  },
  switchShowById(state, id) {
    if (state.loaded.shows) {
      state.selected.index = state.shows.findIndex((s) => s.id === id)
      state.selected.id = id
    }
  },
}

const actions = {
  fetchShows(ctx, data) {
    const authStore = useAuthStore()
    const user = authStore.steeringUser
    ctx.commit('loading', 'shows')
    let uri = createSteeringURL('shows')
    // normal users should only see their own shows, only superusers see all shows
    if (!authStore.isSuperuser) {
      uri += '?owner=' + user.id
    }
    axios
      .get(uri)
      .then((response) => {
        ctx.commit('setShows', response.data)
        ctx.commit('finishLoading', 'shows')
        if (data && typeof data.callback === 'function') {
          data.callback()
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not load shows')
        console.error(error)
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  fetchSchedule(ctx, data) {
    ctx.commit('loading', 'schedule')
    const uri = createSteeringURL('shows', data.show, 'schedules', data.schedule)
    axios
      .get(uri)
      .then((response) => {
        ctx.commit('setSchedule', response.data)
        ctx.commit('finishLoading', 'schedule')
        if (data && typeof data.callback === 'function') {
          data.callback(response)
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not load schedule')
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  fetchSchedules(ctx, data) {
    ctx.commit('loading', 'schedule')
    const uri = createSteeringURL('shows', data.show, 'schedules')
    axios
      .get(uri)
      .then((response) => {
        ctx.commit('setSchedules', response.data)
        ctx.commit('finishLoading', 'schedule')
        if (data && typeof data.callback === 'function') {
          data.callback(response)
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not load schedule')
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  fetchTimeslots(ctx, data) {
    if (data.schedule !== undefined) {
      ctx.commit('loading', 'scheduleTimeslots')
    } else {
      ctx.commit('loading', 'timeslots')
    }

    const query = new URLSearchParams()
    if (data.surrounding) {
      const date = data.surrounding instanceof Date ? data.surrounding : new Date()
      query.set('surrounding', getISODateString(date))
    } else {
      if (has(data, 'start')) query.set('start', data.start)
      if (has(data, 'end')) query.set('end', data.end)
      if (has(data, 'limit')) query.set('limit', data.limit)
      if (has(data, 'offset')) query.set('offset', data.offset)
    }

    const uri =
      data.id && data.schedule
        ? createSteeringURL('shows', data.id, 'schedules', data.schedule, 'timeslots', query)
        : data.id
        ? createSteeringURL('shows', data.id, 'timeslots', query)
        : createSteeringURL('timeslots', query)

    axios
      .get(uri)
      .then((response) => {
        if (data.schedule !== undefined) {
          ctx.commit('setScheduleTimeslots', response.data)
          ctx.commit('finishLoading', 'scheduleTimeslots')
        } else {
          if (data.limit) {
            ctx.commit('setTimeslots', response.data.results)
          } else {
            ctx.commit('setTimeslots', response.data)
          }
          ctx.commit('finishLoading', 'timeslots')
        }
        if (data && typeof data.callback === 'function') {
          data.callback(response)
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not load timeslots')
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  fetchNotes(ctx, data) {
    ctx.commit('loading', 'notes')
    // we only have to make an API call if there are actually any notes
    // otherwise the notes are just and empty array
    if (data.notes.length === 0) {
      ctx.commit('setNotes', [])
      ctx.commit('finishLoading', 'notes')
      return
    }
    const uri = createSteeringURL(
      'shows',
      data.id,
      'notes',
      new URLSearchParams({ ids: data.notes.join(',') }),
    )
    this.$log.debug('fetchNotes: uri:', uri)
    axios
      .get(uri)
      .then((response) => {
        ctx.commit('setNotes', response.data)
        ctx.commit('finishLoading', 'notes')
        if (data && typeof data.callback === 'function') {
          data.callback(response)
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not load notes')
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  fetchMetaArray(ctx, data) {
    ctx.commit('loading', data.property)
    let uri = createSteeringURL(data.property)
    if (data.onlyActive === true) {
      uri += '?active=true'
    }
    axios
      .get(uri)
      .then((response) => {
        ctx.commit('setMetaArray', { property: data.property, value: response.data })
        ctx.commit('finishLoading', data.property)
        if (data && typeof data.callback === 'function') {
          data.callback()
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not load ' + data.property)
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  submitSchedule(ctx, data) {
    ctx.commit('loading', 'schedules')
    const uri = createSteeringURL('shows', data.showId, 'schedules')
    return axios
      .post(uri, data.schedule)
      .then((response) => {
        ctx.commit('finishLoading', 'schedules')
        return callOrReturn(response, data?.callback)
      })
      .catch((error) => {
        APIError.handle(error, 'Unable to submit schedule', data, this)
      })
  },

  submitShow(ctx, data) {
    const uri = createSteeringURL('shows')
    axios
      .post(uri, data.show)
      .then((response) => {
        ctx.commit('addShow', response.data)
        if (data && typeof data.callback === 'function') {
          data.callback(response)
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not add new show')
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  submitNote(ctx, data) {
    const NON_UPDATEABLE_PROPERTIES = ['thumbnails', 'width', 'height']

    let uri = createSteeringURL(
      'shows',
      data.id,
      'schedules',
      data.scheduleID,
      'timeslots',
      data.timeslotID,
      'note',
    )
    if (data.update) {
      uri += data.note.id + '/'
    }

    const method = data.update ? 'put' : 'post'
    const formData = new FormData()

    // We serialize the data to a FormData object, so we can send the image
    // as binary blob to the Steering API.
    for (const [key, value] of Object.entries(data.note)) {
      // When we're updating we don't want to include some properties that cause errors.
      if (data.update && NON_UPDATEABLE_PROPERTIES.includes(key)) {
        continue
      }

      formData.append(key, value)
    }

    axios
      .request({
        url: uri,
        method: method,
        data: formData,
        responseType: 'json', // we need this explicitly here, as it does not seem to work automagically as in GET and PUT requests
      })
      .then((response) => {
        if (data.update) {
          ctx.commit('setNote', response.data)
        } else {
          ctx.commit('addNote', response.data)
        }
        if (data && typeof data.callback === 'function') {
          data.callback(response)
        }
      })
      .catch((error) => {
        const msg = data.update ? 'could not update note' : 'could not add new note'
        handleApiError(this, error, msg)
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  updateShow(ctx, data) {
    const uri = createSteeringURL('shows', data.id)
    axios
      .put(uri, data.show)
      .then((response) => {
        if (data && typeof data.callback === 'function') {
          data.callback(response)
        }
      })
      .catch((error) => {
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel(error.response)
        }
      })
  },

  updateProperty(ctx, data) {
    const show = cloneMinimalShowObject(ctx.getters.getShowByDataParam(data))
    show[data.property] = data.value
    ctx.dispatch('updateShow', {
      id: data.id,
      show: show,
      callback: () => {
        ctx.commit('setProperty', {
          id: data.id,
          property: data.property,
          value: data.value,
        })

        if (typeof data.callback === 'function') {
          data.callback()
        }
      },
      callbackCancel: (error) => {
        if (typeof data.callbackCancel === 'function') {
          data.callbackCancel(error)
        }
      },
    })
  },

  updateImage(ctx, data) {
    const show = ctx.getters.getShowByDataParam(data)
    const uri = createSteeringURL('shows', data.id)
    const formData = new FormData()
    // these propoerties have to be sent always (and they must not be null)
    formData.append('name', show.name)
    formData.append('slug', show.slug)
    formData.append('short_description', show.short_description)
    formData.append('type', show.type)
    formData.append('fundingcategory', show.fundingcategory)
    formData.append('is_active', show.is_active)
    // now we append the new logo/image file (signified by data.type)
    formData.append(data.type, data.file, data.file.name)
    axios
      .put(uri, formData)
      .then((response) => {
        ctx.commit('setProperty', {
          id: data.id,
          property: data.type,
          // when updating images of a show the steering API does not return
          // the full URI but only the path on the server
          // TODO: create an issue in steering, to create consistency
          value: import.meta.env.VUE_APP_BASEURI_STEERING + response.data[data.type],
        })
        if (data && typeof data.callback === 'function') {
          data.callback(response)
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not update show ' + data.type)
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  updateTimeslot(ctx, data) {
    const uri = createSteeringURL(
      'shows',
      data.show,
      'schedules',
      data.schedule,
      'timeslots',
      data.timeslot.id,
    )
    axios
      .put(uri, data.timeslot)
      .then(() => {
        ctx.commit('setTimeslot', data.timeslot)
        if (data && typeof data.callback === 'function') {
          data.callback()
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not update timeslot')
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  deleteSchedule(ctx, data) {
    const uri = createSteeringURL('shows', data.show, 'schedules', data.schedule)
    axios
      .delete(uri)
      .then(() => {
        if (data && typeof data.callback === 'function') {
          data.callback()
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not delete full schedule')
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },

  deleteTimeslot(ctx, data) {
    const uri = createSteeringURL(
      'shows',
      data.show,
      'schedules',
      data.schedule,
      'timeslots',
      data.timeslot,
    )
    axios
      .delete(uri)
      .then(() => {
        if (data && typeof data.callback === 'function') {
          data.callback()
        }
      })
      .catch((error) => {
        handleApiError(this, error, 'could not delete single timeslot')
        if (data && typeof data.callbackCancel === 'function') {
          data.callbackCancel()
        }
      })
  },
}

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