// announcement.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache-2.0

'use strict';

const mongoose = require('mongoose');
const Feed = mongoose.model('Feed');
const FeedEntry = mongoose.model('FeedEntry');

const moment = require('moment');

const { SiteService, SiteError, SiteAsync } = require('../../lib/site-lib');
const { read: feedReader } = require('feed-reader');
const { Feed: FeedGenerator } = require('feed');

class FeedService extends SiteService {

  constructor (dtp) {
    super(dtp, module.exports);

    this.populateFeedEntry = [
      {
        path: 'feed',
      },
    ];
  }

  async start ( ) {
    this.jobQueue = await this.getJobQueue('newsroom', this.dtp.config.jobQueues.newsroom);
  }

  middleware (options) {
    options = Object.assign({ maxEntryCount: 5 }, options);
    return async (req, res, next) => {
      if (this.isSystemRoute(req.path)) {
        return next(); // don't load newsfeeds for non-content routes
      }
      res.locals.newsfeed = await this.getNewsfeed({ skip: 0, cpp: options.maxEntryCount });
      return next();
    };
  }

  async create (feedDefinition) {
    feedDefinition.url = feedDefinition.url.trim();
    const feedContent = await this.load(feedDefinition.url);
    if (!feedContent) {
      throw new SiteError(404, 'Feed failed to load');
    }

    const feed = new Feed();
    feed.url = feedDefinition.url;
    feed.title = feedDefinition.title || feedContent.title || 'New Feed';
    feed.link = feedDefinition.link || feedContent.link;
    feed.description = feedDefinition.description || feedContent.description;
    feed.language = feedContent.language;
    feed.generator = feedContent.generator;
    feed.published = feedContent.published;
    await feed.save();

    this.jobQueue.add('update-feed', { feedId: feed._id });

    return feed.toObject();
  }

  async update (feed, feedDefinition) {
    feedDefinition.url = feedDefinition.url.trim();
    const feedContent = await this.load(feedDefinition.url);
    if (!feedContent) {
      throw new SiteError(404, 'Feed failed to load');
    }

    const updateOp = { $set: { }, $unset: { } };

    updateOp.$set.url = feedDefinition.url;
    updateOp.$set.title = feedDefinition.title || feedContent.title || 'New Feed';
    updateOp.$set.link = feedDefinition.link || feedContent.link;
    updateOp.$set.description = feedDefinition.description || feedContent.description;

    updateOp.$set.language = feedDefinition.language || feedContent.language;
    updateOp.$set.generator = feedDefinition.generator || feedContent.generator;

    await Feed.updateOne({ _id: feed._id }, updateOp);

    this.jobQueue.add('update-feed', { feedId: feed._id });
  }

  async getFeeds (pagination, options) {
    options = Object.assign({ withEntries: false, entryCount: 3 }, options);
    pagination = Object.assign({ skip: 0, cpp: 10 }, pagination);
    const feeds = await Feed
      .find()
      .sort({ title: 1 })
      .skip(pagination.skip)
      .limit(pagination.cpp)
      .lean();

    if (options.withEntries) {
      await SiteAsync.each(feeds, async (feed) => {
        try {
          feed.recent = await this.getFeedEntries(feed, { skip: 0, cpp: options.entryCount });
          this.log.debug('feed entries', { count: feed.recent.entries.length });
        } catch (error) {
          this.log.error('failed to populate recent entries for feed', { feedId: feed._id, error });
          // fall through
        }
      }, 2);
    }

    const totalFeedCount = await Feed.countDocuments();

    return { feeds, totalFeedCount };
  }

  async getById (feedId) {
    const feed = await Feed.findOne({ _id: feedId }).lean();
    return feed;
  }

  async getFeedEntries (feed, pagination) {
    pagination = Object.assign({ skip: 0, cpp: 10 }, pagination);
    const entries = await FeedEntry
      .find({ feed: feed._id })
      .sort({ published: -1 })
      .skip(pagination.skip)
      .limit(pagination.cpp)
      .populate(this.populateFeedEntry)
      .lean();
    const totalFeedEntryCount = await FeedEntry.countDocuments({ feed: feed._id });
    return { entries, totalFeedEntryCount };
  }

  async getNewsfeed (pagination) {
    pagination = Object.assign({ skip: 0, cpp: 5 }, pagination);
    const entries = await FeedEntry
      .find()
      .sort({ published: -1 })
      .skip(pagination.skip)
      .limit(pagination.cpp)
      .populate(this.populateFeedEntry)
      .lean();
    const totalFeedEntryCount = await FeedEntry.estimatedDocumentCount();
    return { entries, totalFeedEntryCount };
  }

  async remove (feed) {
    this.log.info('removing all feed entries', { feedId: feed._id, title: feed.title });
    await FeedEntry.deleteMany({ feed: feed._id });

    this.log.info('removing feed', { feedId: feed._id, title: feed.title });
    await Feed.deleteOne({ _id: feed._id });
  }

  async load (url) {
    const response = await feedReader(url);
    return response;
  }

  async createEntry (feed, entryDefinition) {
    const NOW = new Date();
    const updateOp = { $setOnInsert: { }, $set: { }, $unset: { } };

    updateOp.$setOnInsert.feed = feed._id;
    updateOp.$setOnInsert.link = entryDefinition.link.trim();
    updateOp.$setOnInsert.published = new Date(entryDefinition.published || NOW);

    updateOp.$set.title = entryDefinition.title.trim();
    updateOp.$set.description = entryDefinition.description.trim();

    await FeedEntry.updateOne(
      {
        feed: feed._id,
        link: updateOp.$setOnInsert.link,
      },
      updateOp,
      { upsert: true },
    );
  }

  async getSiteFeed ( ) {
    const { post: postService } = this.dtp.services;
    const posts = await postService.getPosts({ skip: 0, cpp: 10 });

    const siteDomain = this.dtp.config.site.domain;
    const siteDomainKey = this.dtp.config.site.domainKey;
    const siteUrl = `https://${siteDomain}`;

    const cardUrl = `${siteUrl}/img/social-cards/${siteDomainKey}.png`;
    const iconUrl = `${siteUrl}/img/icon/${siteDomainKey}/icon-512x512.png`;

    const feed = new FeedGenerator({
      title: this.dtp.config.site.name,
      description: this.dtp.config.site.description,
      id: siteUrl,
      link: siteUrl,
      language: 'en-US',
      image: cardUrl,
      favicon: iconUrl,
      copyright: `Copyright &copy; ${moment(new Date()).format('YYYY')}, ${this.dtp.config.site.company}`,
      updated: posts[0].updated || posts[0].created,
      generator: 'DTP Sites',
      feedLinks: {
        json: `${siteUrl}/feed/json`,
        atom: `${siteUrl}/feed/json`,
      },
    });

    const authorsAttributed = { };
    posts.forEach((post) => {
      const postUrl = `${siteUrl}/post/${post.slug}`;
      if (!authorsAttributed[post.author._id]) {
        authorsAttributed[post.author._id] = true;
        feed.addContributor({
          name: post.author.displayName || post.author.username,
          link: `${siteUrl}/user/${post.author.username}`,
        });
      }
      feed.addItem({
        title: post.title,
        id: postUrl,
        link: postUrl,
        date: post.updated || post.created,
        description: post.summary,
        image: post.image ? `${siteUrl}/image/${post.image._id}` : `${siteUrl}/img/default-poster.jpg`,
        content: post.content,
        author: [
          {
            name: post.author.displayName || post.author.username,
            link: `${siteUrl}/user/${post.author.username}`,
          },
        ],
      });
    });

    return feed;
  }
}

module.exports = {
  slug: 'feed',
  name: 'feed',
  create: (dtp) => { return new FeedService(dtp); },
};