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

'use strict';

const mongoose = require('mongoose');
const Attachment = mongoose.model('Attachment');

const { SiteService } = require('../../lib/site-lib');

class AttachmentService extends SiteService {

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

  async start ( ) {
    await super.start();

    const { user: userService } = this.dtp.services;
    this.populateAttachment = [
      {
        path: 'item',
      },
      {
        path: 'owner',
        select: userService.USER_SELECT,
      },
    ];

    this.queue = this.getJobQueue('media', this.dtp.config.jobQueues.media);
    // this.template = this.loadViewTemplate('attachment/components/attachment-standalone.pug');
  }

  async create (owner, attachmentDefinition, file) {
    const { minio: minioService } = this.dtp.services;
    const NOW = new Date();

    /*
     * Fill in as much of the attachment as we can prior to uploading
     */

    let attachment = new Attachment();
    attachment.created = NOW;

    attachment.ownerType = owner.type;
    attachment.owner = owner._id;

    attachment.itemType = attachmentDefinition.itemType;
    attachment.item = mongoose.Types.ObjectId(attachmentDefinition.item._id || attachmentDefinition.item);

    attachment.flags.isSensitive = attachmentDefinition.isSensitive === 'on';

    /*
     * Upload the original file to storage
     */

    const attachmentId = attachment._id.toString();

    attachment.original.bucket = process.env.MINIO_ATTACHMENT_BUCKET || 'dtp-attachments';
    attachment.original.key = this.getAttachmentKey(attachment, 'original');
    attachment.original.mime = file.mimetype;
    attachment.original.size = file.size;

    const response = await minioService.uploadFile({
      bucket: attachment.file.bucket,
      key: attachment.file.key,
      filePath: file.path,
      metadata: {
        'X-DTP-Attachment-ID': attachmentId,
        'Content-Type': attachment.metadata.mime,
        'Content-Length': file.size,
      },
    });

    /*
     * Complete the attachment definition, and save it.
     */

    attachment.original.etag = response.etag;
    await attachment.save();

    attachment = await this.getById(attachment._id);

    await this.queue.add('attachment-ingest', { attachmentId: attachment._id });

    return attachment;
  }

  getAttachmentKey (attachment, slug) {
    const attachmentId = attachment._id.toString();
    const prefix = attachmentId.slice(-4); // last 4 for best entropy
    return `/attachment/${prefix}/${attachmentId}/${attachmentId}-${slug}}`;
  }

  /**
   * Retrieves populated Attachment documents attached to an item.
   * @param {String} itemType The type of item (ex: 'ChatMessage')
   * @param {*} itemId The _id of the item (ex: message._id)
   * @returns Array of attachments associated with the item.
   */
  async getForItem (itemType, itemId) {
    const attachments = await Attachment
      .find({ itemType, item: itemId })
      .sort({ order: 1, created: 1 })
      .populate(this.populateAttachment)
      .lean();
    return attachments;
  }

  /**
   * Retrieves populated Attachment documents created by a specific owner.
   * @param {User} owner The owner for which Attachments are being fetched.
   * @param {*} pagination Optional pagination of data set
   * @returns Array of attachments owned by the specified owner.
   */
  async getForOwner (owner, pagination) {
    const attachments = await Attachment
      .find({ ownerType: owner.type, owner: owner._id })
      .sort({ order: 1, created: 1 })
      .skip(pagination.skip)
      .limit(pagination.cpp)
      .populate(this.populateAttachment)
      .lean();
    return attachments;
  }

  /**
   * 
   * @param {mongoose.Types.ObjectId} attachmentId The ID of the attachment
   * @param {Object} options `withOriginal` true|false
   * @returns A populated Attachment document configured per options.
   */
  async getById (attachmentId, options) {
    options = Object.assign({
      withOriginal: false,
    }, options || { });

    let q = Attachment.findById(attachmentId);
    if (options.withOriginal) {
      q = q.select('+original');
    }

    const attachment = await q.populate(this.populateAttachment).lean();
    return attachment;
  }

  /**
   * Updates the status of an Attachment.
   * @param {Attachment} attachment The attachment being modified.
   * @param {*} status The new status of the attachment
   */
  async setStatus (attachment, status) {
    await Attachment.updateOne({ _id: attachment._id }, { $set: { status } });
  }

  /**
   * Passes an attachment and options through a Pug template to generate HTML
   * output ready to be inserted into a DOM to present the attachment in the UI.
   * @param {Attachment} attachment 
   * @param {Object} attachmentOptions Additional options passed to the template
   * @returns HTML output of the template
   */
  async render (attachment, attachmentOptions) {
    return this.attachmentTemplate({ attachment, attachmentOptions });
  }

  /**
   * Creates a Bull Queue job to delete an Attachment including it's processed
   * and original media files.
   * @param {Attachment} attachment The attachment to be deleted.
   * @returns Bull Queue job handle for the newly created job to delete the
   * attachment.
   */
  async remove (attachment) {
    this.log.info('creating job to delete attachment', { attachmentId: attachment._id });
    return await this.queue.add('attachment-delete', { attachmentId: attachment._id });
  }
}

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