const Path = require('path')

const Logger = require('../Logger')
const SocketAuthority = require('../SocketAuthority')

const fs = require('../libs/fsExtra')
const Feed = require('../objects/Feed')

class RssFeedManager {
  constructor(db) {
    this.db = db

    this.feeds = {}
  }

  get feedsArray() {
    return Object.values(this.feeds)
  }

  validateFeedEntity(feedObj) {
    if (feedObj.entityType === 'collection') {
      if (!this.db.collections.some(li => li.id === feedObj.entityId)) {
        Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Collection "${feedObj.entityId}" not found`)
        return false
      }
    } else if (feedObj.entityType === 'libraryItem') {
      if (!this.db.libraryItems.some(li => li.id === feedObj.entityId)) {
        Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Library item "${feedObj.entityId}" not found`)
        return false
      }
    } else if (feedObj.entityType === 'series') {
      const series = this.db.series.find(s => s.id === feedObj.entityId)
      const hasSeriesBook = this.db.libraryItems.some(li => li.mediaType === 'book' && li.media.metadata.hasSeries(series.id) && li.media.tracks.length)
      if (!hasSeriesBook) {
        Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Series "${feedObj.entityId}" not found or has no audio tracks`)
        return false
      }
    } else {
      Logger.error(`[RssFeedManager] Removing feed "${feedObj.id}". Invalid entityType "${feedObj.entityType}"`)
      return false
    }
    return true
  }

  async init() {
    const feedObjects = await this.db.getAllEntities('feed')
    if (!feedObjects || !feedObjects.length) return

    for (const feedObj of feedObjects) {
      // Migration: In v2.2.12 entityType "item" was updated to "libraryItem"
      if (feedObj.entityType === 'item') {
        feedObj.entityType = 'libraryItem'
        await this.db.updateEntity('feed', feedObj)
      }

      // Remove invalid feeds
      if (!this.validateFeedEntity(feedObj)) {
        await this.db.removeEntity('feed', feedObj.id)
      }

      const feed = new Feed(feedObj)
      this.feeds[feed.id] = feed
      Logger.info(`[RssFeedManager] Opened rss feed ${feed.feedUrl}`)
    }
  }

  findFeedForEntityId(entityId) {
    return Object.values(this.feeds).find(feed => feed.entityId === entityId)
  }

  findFeed(feedId) {
    return this.feeds[feedId] || null
  }

  async getFeed(req, res) {
    const feed = this.feeds[req.params.id]
    if (!feed) {
      Logger.debug(`[RssFeedManager] Feed not found ${req.params.id}`)
      res.sendStatus(404)
      return
    }

    // Check if feed needs to be updated
    if (feed.entityType === 'libraryItem') {
      const libraryItem = this.db.getLibraryItem(feed.entityId)

      let mostRecentlyUpdatedAt = libraryItem.updatedAt
      if (libraryItem.isPodcast) {
        libraryItem.media.episodes.forEach((episode) => {
          if (episode.updatedAt > mostRecentlyUpdatedAt) mostRecentlyUpdatedAt = episode.updatedAt
        })
      }

      if (libraryItem && (!feed.entityUpdatedAt || mostRecentlyUpdatedAt > feed.entityUpdatedAt)) {
        Logger.debug(`[RssFeedManager] Updating RSS feed for item ${libraryItem.id} "${libraryItem.media.metadata.title}"`)
        feed.updateFromItem(libraryItem)
        await this.db.updateEntity('feed', feed)
      }
    } else if (feed.entityType === 'collection') {
      const collection = this.db.collections.find(c => c.id === feed.entityId)
      if (collection) {
        const collectionExpanded = collection.toJSONExpanded(this.db.libraryItems)

        // Find most recently updated item in collection
        let mostRecentlyUpdatedAt = collectionExpanded.lastUpdate
        collectionExpanded.books.forEach((libraryItem) => {
          if (libraryItem.media.tracks.length && libraryItem.updatedAt > mostRecentlyUpdatedAt) {
            mostRecentlyUpdatedAt = libraryItem.updatedAt
          }
        })

        if (!feed.entityUpdatedAt || mostRecentlyUpdatedAt > feed.entityUpdatedAt) {
          Logger.debug(`[RssFeedManager] Updating RSS feed for collection "${collection.name}"`)

          feed.updateFromCollection(collectionExpanded)
          await this.db.updateEntity('feed', feed)
        }
      }
    } else if (feed.entityType === 'series') {
      const series = this.db.series.find(s => s.id === feed.entityId)
      if (series) {
        const seriesJson = series.toJSON()
        // Get books in series that have audio tracks
        seriesJson.books = this.db.libraryItems.filter(li => li.mediaType === 'book' && li.media.metadata.hasSeries(series.id) && li.media.tracks.length)

        // Find most recently updated item in series
        let mostRecentlyUpdatedAt = seriesJson.updatedAt
        let totalTracks = 0 // Used to detect series items removed
        seriesJson.books.forEach((libraryItem) => {
          totalTracks += libraryItem.media.tracks.length
          if (libraryItem.media.tracks.length && libraryItem.updatedAt > mostRecentlyUpdatedAt) {
            mostRecentlyUpdatedAt = libraryItem.updatedAt
          }
        })
        if (totalTracks !== feed.episodes.length) {
          mostRecentlyUpdatedAt = Date.now()
        }

        if (!feed.entityUpdatedAt || mostRecentlyUpdatedAt > feed.entityUpdatedAt) {
          Logger.debug(`[RssFeedManager] Updating RSS feed for series "${seriesJson.name}"`)

          feed.updateFromSeries(seriesJson)
          await this.db.updateEntity('feed', feed)
        }
      }
    }

    const xml = feed.buildXml()
    res.set('Content-Type', 'text/xml')
    res.send(xml)
  }

  getFeedItem(req, res) {
    const feed = this.feeds[req.params.id]
    if (!feed) {
      Logger.debug(`[RssFeedManager] Feed not found ${req.params.id}`)
      res.sendStatus(404)
      return
    }
    const episodePath = feed.getEpisodePath(req.params.episodeId)
    if (!episodePath) {
      Logger.error(`[RssFeedManager] Feed episode not found ${req.params.episodeId}`)
      res.sendStatus(404)
      return
    }
    res.sendFile(episodePath)
  }

  getFeedCover(req, res) {
    const feed = this.feeds[req.params.id]
    if (!feed) {
      Logger.debug(`[RssFeedManager] Feed not found ${req.params.id}`)
      res.sendStatus(404)
      return
    }

    if (!feed.coverPath) {
      res.sendStatus(404)
      return
    }

    const extname = Path.extname(feed.coverPath).toLowerCase().slice(1)
    res.type(`image/${extname}`)
    const readStream = fs.createReadStream(feed.coverPath)
    readStream.pipe(res)
  }

  async openFeedForItem(user, libraryItem, options) {
    const serverAddress = options.serverAddress
    const slug = options.slug
    const preventIndexing = options.metadataDetails?.preventIndexing ?? true
    const ownerName = options.metadataDetails?.ownerName
    const ownerEmail = options.metadataDetails?.ownerEmail

    const feed = new Feed()
    feed.setFromItem(user.id, slug, libraryItem, serverAddress, preventIndexing, ownerName, ownerEmail)
    this.feeds[feed.id] = feed

    Logger.debug(`[RssFeedManager] Opened RSS feed "${feed.feedUrl}"`)
    await this.db.insertEntity('feed', feed)
    SocketAuthority.emitter('rss_feed_open', feed.toJSONMinified())
    return feed
  }

  async openFeedForCollection(user, collectionExpanded, options) {
    const serverAddress = options.serverAddress
    const slug = options.slug
    const preventIndexing = options.metadataDetails?.preventIndexing ?? true
    const ownerName = options.metadataDetails?.ownerName
    const ownerEmail = options.metadataDetails?.ownerEmail

    const feed = new Feed()
    feed.setFromCollection(user.id, slug, collectionExpanded, serverAddress, preventIndexing, ownerName, ownerEmail)
    this.feeds[feed.id] = feed

    Logger.debug(`[RssFeedManager] Opened RSS feed "${feed.feedUrl}"`)
    await this.db.insertEntity('feed', feed)
    SocketAuthority.emitter('rss_feed_open', feed.toJSONMinified())
    return feed
  }

  async openFeedForSeries(user, seriesExpanded, options) {
    const serverAddress = options.serverAddress
    const slug = options.slug
    const preventIndexing = options.metadataDetails?.preventIndexing ?? true
    const ownerName = options.metadataDetails?.ownerName
    const ownerEmail = options.metadataDetails?.ownerEmail

    const feed = new Feed()
    feed.setFromSeries(user.id, slug, seriesExpanded, serverAddress, preventIndexing, ownerName, ownerEmail)
    this.feeds[feed.id] = feed

    Logger.debug(`[RssFeedManager] Opened RSS feed "${feed.feedUrl}"`)
    await this.db.insertEntity('feed', feed)
    SocketAuthority.emitter('rss_feed_open', feed.toJSONMinified())
    return feed
  }

  async handleCloseFeed(feed) {
    if (!feed) return
    await this.db.removeEntity('feed', feed.id)
    SocketAuthority.emitter('rss_feed_closed', feed.toJSONMinified())
    delete this.feeds[feed.id]
    Logger.info(`[RssFeedManager] Closed RSS feed "${feed.feedUrl}"`)
  }

  closeRssFeed(id) {
    if (!this.feeds[id]) return
    return this.handleCloseFeed(this.feeds[id])
  }

  closeFeedForEntityId(entityId) {
    const feed = this.findFeedForEntityId(entityId)
    if (!feed) return
    return this.handleCloseFeed(feed)
  }
}
module.exports = RssFeedManager