const moment = require('moment');
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
const emailService = require('../email-service');
const CommentsServiceEmailRenderer = require('./comments-service-email-renderer');
const {toPlain} = require('../../lib/common/to-plain');
const {t} = require('../i18n');

class CommentsServiceEmails {
    constructor({config, logging, models, mailer, settingsCache, settingsHelpers, urlService, urlUtils}) {
        this.config = config;
        this.logging = logging;
        this.models = models;
        this.mailer = mailer;
        this.settingsCache = settingsCache;
        this.settingsHelpers = settingsHelpers;
        this.urlService = urlService;
        this.urlUtils = urlUtils;
        this.commentsServiceEmailRenderer = new CommentsServiceEmailRenderer({t});
    }

    /**
     * Build the post URL with comment fragment for email links
     * @param {{id: string}} post - The post (model or plain object with `id`)
     * @param {string} commentId - The ID of the comment to link to
     * @returns {string} The post URL with appropriate comment fragment
     */
    getPostUrl(post, commentId) {
        const postData = toPlain(post);
        // Comments live on pages as well as posts; the URL service routes by
        // the passed type, so a page typed 'posts' resolves to /404/.
        const routerType = postData.type === 'page' ? 'pages' : 'posts';
        const baseUrl = this.urlService.facade.getUrlForResource({...postData, type: routerType}, {absolute: true});
        return `${baseUrl}#ghost-comments-${commentId}`;
    }

    async notifyPostAuthors(comment) {
        // withRelated tags+authors so getPostUrl can resolve `:primary_tag` /
        // `:primary_author` permalinks (only sites using those custom
        // permalinks need it, but loading the relations is cheap and
        // guarantees correctness regardless of the site's routes.yaml).
        const post = await this.models.Post.findOne({id: comment.get('post_id')}, {withRelated: ['tags', 'authors']});
        const member = await this.models.Member.findOne({id: comment.get('member_id')});

        for (const author of post.related('authors')) {
            if (!author.get('comment_notifications')) {
                continue;
            }

            // Don't send a notification if you comment or reply as a member to your own post
            if (author.get('email') === member.get('email')) {
                continue;
            }

            const to = author.get('email');
            const subject = '💬 New comment on your post: ' + post.get('title');

            const memberName = member.get('name') || 'Anonymous';

            const templateData = {
                siteTitle: this.settingsCache.get('title'),
                siteUrl: this.urlUtils.getSiteUrl(),
                siteDomain: this.siteDomain,
                postTitle: post.get('title'),
                postUrl: this.getPostUrl(post, comment.get('id')),
                commentHtml: comment.get('html'),
                commentDate: moment(comment.get('created_at')).tz(this.settingsCache.get('timezone')).format('D MMM YYYY'),
                memberName: memberName,
                memberExpertise: member.get('expertise'),
                memberInitials: this.extractInitials(memberName),
                accentColor: this.settingsCache.get('accent_color'),
                fromEmail: this.notificationFromAddress,
                toEmail: to,
                staffUrl: this.urlUtils.urlJoin(this.urlUtils.urlFor('admin', true), '#', `/settings/staff/${author.get('slug')}/email-notifications`)
            };

            const {
                html,
                text
            } = await this.commentsServiceEmailRenderer.renderEmailTemplate('new-comment', templateData);

            await this.sendMail({
                to,
                subject,
                html,
                text
            });
        }
    }

    async notifyParentCommentAuthor(reply, {type = 'parent'} = {}) {
        let parent;
        if (type === 'in_reply_to') {
            parent = await this.models.Comment.findOne({id: reply.get('in_reply_to_id')});
        } else {
            parent = await this.models.Comment.findOne({id: reply.get('parent_id')});
        }
        const parentMember = parent.related('member');

        if (parent?.get('status') !== 'published' || !parentMember.get('enable_comment_notifications')) {
            return;
        }

        const memberId = reply.get('member_id');

        // Don't send a notification if you reply to your own comment
        if (parentMember.id === memberId) {
            return;
        }

        const parentMemberEmail = parentMember.get('email');
        const subject = '↪️ ' + t(`New reply to your comment on {siteTitle}`, {
            siteTitle: this.settingsCache.get('title'),
            interpolation: {escapeValue: false}
        });

        const post = await this.models.Post.findOne({id: reply.get('post_id')}, {withRelated: ['tags', 'authors']});
        const member = await this.models.Member.findOne({id: memberId});

        const memberName = member.get('name') || 'Anonymous';

        const templateData = {
            siteTitle: this.settingsCache.get('title'),
            siteUrl: this.urlUtils.getSiteUrl(),
            siteDomain: this.siteDomain,
            postTitle: post.get('title'),
            postUrl: this.getPostUrl(post, reply.get('id')),
            replyHtml: reply.get('html'),
            replyDate: moment(reply.get('created_at')).tz(this.settingsCache.get('timezone')).format('D MMM YYYY'),
            memberName: memberName,
            memberExpertise: member.get('expertise'),
            memberInitials: this.extractInitials(memberName),
            accentColor: this.settingsCache.get('accent_color'),
            fromEmail: this.notificationFromAddress,
            toEmail: parentMemberEmail,
            profileUrl: emailService.renderer.createUnsubscribeUrl(parentMember.get('uuid'), {comments: true})
        };

        const {
            html,
            text
        } = await this.commentsServiceEmailRenderer.renderEmailTemplate('new-comment-reply', templateData);

        return this.sendMail({
            to: parentMemberEmail,
            subject,
            html,
            text
        });
    }

    /**
     * Send an email to notify the owner of the site that a comment has been reported by a member
     * @param {*} comment The comment model that has been reported
     * @param {*} reporter The member object who reported this comment
     */
    async notifyReport(comment, reporter) {
        const post = await this.models.Post.findOne({id: comment.get('post_id')}, {withRelated: ['tags', 'authors']});
        const member = await this.models.Member.findOne({id: comment.get('member_id')});
        const owner = await this.models.User.getOwnerUser();

        // For now we only send the report to the owner
        const to = owner.get('email');
        const subject = '🚩 A comment has been reported on your post';

        const memberName = member.get('name') || 'Anonymous';

        const templateData = {
            siteTitle: this.settingsCache.get('title'),
            siteUrl: this.urlUtils.getSiteUrl(),
            siteDomain: this.siteDomain,
            postTitle: post.get('title'),
            postUrl: this.getPostUrl(post, comment.get('id')),
            commentHtml: comment.get('html'),
            commentText: htmlToPlaintext.comment(comment.get('html')),
            commentDate: moment(comment.get('created_at')).tz(this.settingsCache.get('timezone')).format('D MMM YYYY'),

            reporterName: reporter.name,
            reporterEmail: reporter.email,
            reporter: reporter.name ? `${reporter.name} (${reporter.email})` : reporter.email,

            memberName: memberName,
            memberEmail: member.get('email'),
            memberExpertise: member.get('expertise'),
            memberInitials: this.extractInitials(memberName),
            accentColor: this.settingsCache.get('accent_color'),
            fromEmail: this.notificationFromAddress,
            toEmail: to,
            staffUrl: this.urlUtils.urlJoin(this.urlUtils.urlFor('admin', true), '#', `/settings/staff/${owner.get('slug')}/email-notifications`),
            moderationUrl: this.urlUtils.urlJoin(this.urlUtils.urlFor('admin', true), '#', `/comments/?id=is:${comment.get('id')}`)
        };

        const {html, text} = await this.commentsServiceEmailRenderer.renderEmailTemplate('report', templateData);

        await this.sendMail({
            to,
            subject,
            html,
            text
        });
    }

    // Utils

    get siteDomain() {
        const [, siteDomain] = this.urlUtils.getSiteUrl()
            .match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));

        return siteDomain;
    }

    get notificationFromAddress() {
        return this.settingsHelpers.getMembersSupportAddress();
    }

    extractInitials(name = '') {
        const names = name.split(' ');
        const initials = names.length > 1 ? [names[0][0], names[names.length - 1][0]] : [names[0][0]];
        return initials.join('').toUpperCase();
    }

    async sendMail(message) {
        if (process.env.NODE_ENV !== 'production') {
            this.logging.warn(message.text);
        }

        let msg = Object.assign({
            from: this.notificationFromAddress,
            forceTextContent: true
        }, message);

        return this.mailer.send(msg);
    }
}

module.exports = CommentsServiceEmails;
