const MentionController = require('./mention-controller');
const WebmentionMetadata = require('./webmention-metadata');
const MentionsAPI = require('./mentions-api');
const MentionSendingService = require('./mention-sending-service');
const MentionDiscoveryService = require('./mention-discovery-service');
const BookshelfMentionRepository = require('./bookshelf-mention-repository');
const ResourceService = require('./resource-service');
const RoutingService = require('./routing-service');
const models = require('../../models');
const events = require('../../lib/common/events');
const externalRequest = require('../../../server/lib/request-external.js');
const urlUtils = require('../../../shared/url-utils').default;
const outputSerializerUrlUtil = require('../../../server/api/endpoints/utils/serializers/output/utils/url');
const urlService = require('../url');
const settingsCache = require('../../../shared/settings-cache');
const DomainEvents = require('@tryghost/domain-events');
const jobsService = require('../mentions-jobs');

// Serializes a post model to the data the URL service needs, loading the
// relations it reads for filtered collections (event-emitted models don't
// reliably carry them).
async function getPostData(post) {
    const missing = urlService.facade.getRequiredRelations().filter(relation => !post.relations[relation]);
    if (missing.length) {
        await post.load(missing);
    }
    return post.toJSON();
}

function getPostUrl(id, postData) {
    const jsonModel = {...postData};
    outputSerializerUrlUtil.forPost(id, jsonModel, {options: {}});
    return jsonModel.url;
}

module.exports = {
    /** @type {import('./mentions-api')} */
    api: null,
    /** @type {import('./bookshelf-mention-repository')} */
    repository: null,
    controller: new MentionController(),
    metadata: new WebmentionMetadata(),
    /** @type {import('./mention-sending-service')} */
    sendingService: null,
    didInit: false,
    async init() {
        if (this.didInit) {
            return;
        }
        this.didInit = true;
        const repository = new BookshelfMentionRepository({
            MentionModel: models.Mention,
            DomainEvents
        });
        this.repository = repository;

        const webmentionMetadata = this.metadata;
        const discoveryService = new MentionDiscoveryService({externalRequest});
        const resourceService = new ResourceService({
            urlUtils,
            urlService
        });

        const routingService = new RoutingService({
            siteUrl: new URL(urlUtils.getSiteUrl()),
            resourceService,
            externalRequest
        });

        const api = new MentionsAPI({
            repository,
            webmentionMetadata,
            resourceService,
            routingService
        });

        this.api = api;

        this.controller.init({
            api,
            jobService: {
                async addJob(name, fn) {
                    jobsService.addJob({
                        name,
                        job: fn,
                        offloaded: false
                    });
                }
            },
            mentionResourceService: {
                async getByID(id) {
                    if (!id) {
                        return null;
                    }
                    const post = await models.Post.findOne({id: id.toHexString()});

                    if (!post) {
                        return null;
                    }
                    return {
                        id: id,
                        name: post.get('title'),
                        type: post.get('type')
                    };
                }
            }
        });

        const sendingService = new MentionSendingService({
            discoveryService,
            externalRequest,
            getSiteUrl: () => urlUtils.urlFor('home', true),
            getPostData: post => getPostData(post),
            getPostUrl: (id, data) => getPostUrl(id, data),
            isEnabled: () => !settingsCache.get('is_private'),
            jobService: {
                async addJob(name, fn) {
                    jobsService.addJob({
                        name,
                        job: fn,
                        offloaded: false
                    });
                }
            }
        });
        sendingService.listen(events);

        this.sendingService = sendingService;
    }
};

// exposed for testing
module.exports.getPostData = getPostData;
module.exports.getPostUrl = getPostUrl;
