const { DataTypes, Model, Op, literal } = require('sequelize')
const Logger = require('../Logger')

const oldPlaylist = require('../objects/Playlist')

class Playlist extends Model {
  constructor(values, options) {
    super(values, options)

    /** @type {UUIDV4} */
    this.id
    /** @type {string} */
    this.name
    /** @type {string} */
    this.description
    /** @type {UUIDV4} */
    this.libraryId
    /** @type {UUIDV4} */
    this.userId
    /** @type {Date} */
    this.createdAt
    /** @type {Date} */
    this.updatedAt
  }

  static getOldPlaylist(playlistExpanded) {
    const items = playlistExpanded.playlistMediaItems
      .map((pmi) => {
        const mediaItem = pmi.mediaItem || pmi.dataValues?.mediaItem
        const libraryItemId = mediaItem?.podcast?.libraryItem?.id || mediaItem?.libraryItem?.id || null
        if (!libraryItemId) {
          Logger.error(`[Playlist] Invalid playlist media item - No library item id found`, JSON.stringify(pmi, null, 2))
          return null
        }
        return {
          episodeId: pmi.mediaItemType === 'podcastEpisode' ? pmi.mediaItemId : '',
          libraryItemId
        }
      })
      .filter((pmi) => pmi)

    return new oldPlaylist({
      id: playlistExpanded.id,
      libraryId: playlistExpanded.libraryId,
      userId: playlistExpanded.userId,
      name: playlistExpanded.name,
      description: playlistExpanded.description,
      items,
      lastUpdate: playlistExpanded.updatedAt.valueOf(),
      createdAt: playlistExpanded.createdAt.valueOf()
    })
  }

  /**
   * Get old playlist toJSONExpanded
   * @param {string[]} [include]
   * @returns {Promise<oldPlaylist>} oldPlaylist.toJSONExpanded
   */
  async getOldJsonExpanded(include) {
    this.playlistMediaItems =
      (await this.getPlaylistMediaItems({
        include: [
          {
            model: this.sequelize.models.book,
            include: this.sequelize.models.libraryItem
          },
          {
            model: this.sequelize.models.podcastEpisode,
            include: {
              model: this.sequelize.models.podcast,
              include: this.sequelize.models.libraryItem
            }
          }
        ],
        order: [['order', 'ASC']]
      })) || []

    const oldPlaylist = this.sequelize.models.playlist.getOldPlaylist(this)
    const libraryItemIds = oldPlaylist.items.map((i) => i.libraryItemId)

    let libraryItems = await this.sequelize.models.libraryItem.getAllOldLibraryItems({
      id: libraryItemIds
    })

    const playlistExpanded = oldPlaylist.toJSONExpanded(libraryItems)

    if (include?.includes('rssfeed')) {
      const feeds = await this.getFeeds()
      if (feeds?.length) {
        playlistExpanded.rssFeed = this.sequelize.models.feed.getOldFeed(feeds[0])
      }
    }

    return playlistExpanded
  }

  static createFromOld(oldPlaylist) {
    const playlist = this.getFromOld(oldPlaylist)
    return this.create(playlist)
  }

  static getFromOld(oldPlaylist) {
    return {
      id: oldPlaylist.id,
      name: oldPlaylist.name,
      description: oldPlaylist.description,
      userId: oldPlaylist.userId,
      libraryId: oldPlaylist.libraryId
    }
  }

  static removeById(playlistId) {
    return this.destroy({
      where: {
        id: playlistId
      }
    })
  }

  /**
   * Get playlist by id
   * @param {string} playlistId
   * @returns {Promise<oldPlaylist|null>} returns null if not found
   */
  static async getById(playlistId) {
    if (!playlistId) return null
    const playlist = await this.findByPk(playlistId, {
      include: {
        model: this.sequelize.models.playlistMediaItem,
        include: [
          {
            model: this.sequelize.models.book,
            include: this.sequelize.models.libraryItem
          },
          {
            model: this.sequelize.models.podcastEpisode,
            include: {
              model: this.sequelize.models.podcast,
              include: this.sequelize.models.libraryItem
            }
          }
        ]
      },
      order: [['playlistMediaItems', 'order', 'ASC']]
    })
    if (!playlist) return null
    return this.getOldPlaylist(playlist)
  }

  /**
   * Get old playlists for user and optionally for library
   *
   * @param {string} userId
   * @param {string} [libraryId]
   * @returns {Promise<oldPlaylist[]>}
   */
  static async getOldPlaylistsForUserAndLibrary(userId, libraryId = null) {
    if (!userId && !libraryId) return []
    const whereQuery = {}
    if (userId) {
      whereQuery.userId = userId
    }
    if (libraryId) {
      whereQuery.libraryId = libraryId
    }
    const playlistsExpanded = await this.findAll({
      where: whereQuery,
      include: {
        model: this.sequelize.models.playlistMediaItem,
        include: [
          {
            model: this.sequelize.models.book,
            include: this.sequelize.models.libraryItem
          },
          {
            model: this.sequelize.models.podcastEpisode,
            include: {
              model: this.sequelize.models.podcast,
              include: this.sequelize.models.libraryItem
            }
          }
        ]
      },
      order: [
        [literal('name COLLATE NOCASE'), 'ASC'],
        ['playlistMediaItems', 'order', 'ASC']
      ]
    })

    const oldPlaylists = []
    for (const playlistExpanded of playlistsExpanded) {
      const oldPlaylist = this.getOldPlaylist(playlistExpanded)
      const libraryItems = []
      for (const pmi of playlistExpanded.playlistMediaItems) {
        let mediaItem = pmi.mediaItem || pmi.dataValues.mediaItem

        if (!mediaItem) {
          Logger.error(`[Playlist] Invalid playlist media item - No media item found`, JSON.stringify(mediaItem, null, 2))
          continue
        }
        let libraryItem = mediaItem.libraryItem || mediaItem.podcast?.libraryItem

        if (mediaItem.podcast) {
          libraryItem.media = mediaItem.podcast
          libraryItem.media.podcastEpisodes = [mediaItem]
          delete mediaItem.podcast.libraryItem
        } else {
          libraryItem.media = mediaItem
          delete mediaItem.libraryItem
        }

        const oldLibraryItem = this.sequelize.models.libraryItem.getOldLibraryItem(libraryItem)
        libraryItems.push(oldLibraryItem)
      }
      const oldPlaylistJson = oldPlaylist.toJSONExpanded(libraryItems)
      oldPlaylists.push(oldPlaylistJson)
    }

    return oldPlaylists
  }

  /**
   * Get number of playlists for a user and library
   * @param {string} userId
   * @param {string} libraryId
   * @returns
   */
  static async getNumPlaylistsForUserAndLibrary(userId, libraryId) {
    return this.count({
      where: {
        userId,
        libraryId
      }
    })
  }

  /**
   * Get all playlists for mediaItemIds
   * @param {string[]} mediaItemIds
   * @returns {Promise<Playlist[]>}
   */
  static async getPlaylistsForMediaItemIds(mediaItemIds) {
    if (!mediaItemIds?.length) return []

    const playlistMediaItemsExpanded = await this.sequelize.models.playlistMediaItem.findAll({
      where: {
        mediaItemId: {
          [Op.in]: mediaItemIds
        }
      },
      include: [
        {
          model: this.sequelize.models.playlist,
          include: {
            model: this.sequelize.models.playlistMediaItem,
            include: [
              {
                model: this.sequelize.models.book,
                include: this.sequelize.models.libraryItem
              },
              {
                model: this.sequelize.models.podcastEpisode,
                include: {
                  model: this.sequelize.models.podcast,
                  include: this.sequelize.models.libraryItem
                }
              }
            ]
          }
        }
      ],
      order: [['playlist', 'playlistMediaItems', 'order', 'ASC']]
    })

    const playlists = []
    for (const playlistMediaItem of playlistMediaItemsExpanded) {
      const playlist = playlistMediaItem.playlist
      if (playlists.some((p) => p.id === playlist.id)) continue

      playlist.playlistMediaItems = playlist.playlistMediaItems.map((pmi) => {
        if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
          pmi.mediaItem = pmi.book
          pmi.dataValues.mediaItem = pmi.dataValues.book
        } else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
          pmi.mediaItem = pmi.podcastEpisode
          pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
        }
        delete pmi.book
        delete pmi.dataValues.book
        delete pmi.podcastEpisode
        delete pmi.dataValues.podcastEpisode
        return pmi
      })
      playlists.push(playlist)
    }
    return playlists
  }

  /**
   * Initialize model
   * @param {import('../Database').sequelize} sequelize
   */
  static init(sequelize) {
    super.init(
      {
        id: {
          type: DataTypes.UUID,
          defaultValue: DataTypes.UUIDV4,
          primaryKey: true
        },
        name: DataTypes.STRING,
        description: DataTypes.TEXT
      },
      {
        sequelize,
        modelName: 'playlist'
      }
    )

    const { library, user } = sequelize.models
    library.hasMany(Playlist)
    Playlist.belongsTo(library)

    user.hasMany(Playlist, {
      onDelete: 'CASCADE'
    })
    Playlist.belongsTo(user)

    Playlist.addHook('afterFind', (findResult) => {
      if (!findResult) return

      if (!Array.isArray(findResult)) findResult = [findResult]

      for (const instance of findResult) {
        if (instance.playlistMediaItems?.length) {
          instance.playlistMediaItems = instance.playlistMediaItems.map((pmi) => {
            if (pmi.mediaItemType === 'book' && pmi.book !== undefined) {
              pmi.mediaItem = pmi.book
              pmi.dataValues.mediaItem = pmi.dataValues.book
            } else if (pmi.mediaItemType === 'podcastEpisode' && pmi.podcastEpisode !== undefined) {
              pmi.mediaItem = pmi.podcastEpisode
              pmi.dataValues.mediaItem = pmi.dataValues.podcastEpisode
            }
            // To prevent mistakes:
            delete pmi.book
            delete pmi.dataValues.book
            delete pmi.podcastEpisode
            delete pmi.dataValues.podcastEpisode
            return pmi
          })
        }
      }
    })
  }
}

module.exports = Playlist