'use strict';

const winston = require('winston');

const db = require('../database');
const posts = require('../posts');
const topics = require('../topics');

const activitypub = module.parent.exports;
const Contexts = module.exports;

const acceptableTypes = ['Collection', 'CollectionPage', 'OrderedCollection', 'OrderedCollectionPage'];

Contexts.get = async (uid, id) => {
	let context;
	let type;

	// Generate digest for If-None-Match if locally cached
	const tid = await posts.getPostField(id, 'tid');
	const headers = {};
	if (tid) {
		const [mainPid, pids] = await Promise.all([
			topics.getTopicField(tid, 'mainPid'),
			db.getSortedSetMembers(`tid:${tid}:posts`),
		]);
		pids.push(mainPid);
		const digest = activitypub.helpers.generateDigest(new Set(pids));
		headers['If-None-Match'] = `"${digest}"`;
	}

	try {
		({ context } = await activitypub.get('uid', uid, id, { headers }));
		if (!context) {
			winston.verbose(`[activitypub/context] ${id} contains no context.`);
			return false;
		}
		({ type } = await activitypub.get('uid', uid, context));
	} catch (e) {
		if (e.code === 'ap_get_304') {
			winston.verbose(`[activitypub/context] ${id} context unchanged.`);
			return { tid };
		}

		winston.verbose(`[activitypub/context] ${id} context not resolvable.`);
		return false;
	}

	if (acceptableTypes.includes(type)) {
		return { context };
	}

	return false;
};

Contexts.getItems = async (uid, id, options) => {
	if (!options.hasOwnProperty('root')) {
		options.root = true;
	}

	winston.verbose(`[activitypub/context] Retrieving context ${id}`);
	let { type, items, orderedItems, first, next } = await activitypub.get('uid', uid, id);
	if (!acceptableTypes.includes(type)) {
		return [];
	}

	if (type.startsWith('Ordered') && orderedItems) {
		items = orderedItems;
	}

	if (items) {
		items = await Promise.all(items
			.map(async item => (activitypub.helpers.isUri(item) ? parseString(uid, item) : parseItem(uid, item))));
		items = items.filter(Boolean);
		winston.verbose(`[activitypub/context] Found ${items.length} items.`);
	}

	const chain = new Set(items || []);
	if (!next && options.root && first) {
		next = first;
	}

	if (next) {
		winston.verbose('[activitypub/context] Fetching next page...');
		Array
			.from(await Contexts.getItems(uid, next, {
				...options,
				root: false,
			}))
			.forEach((item) => {
				chain.add(item);
			});
	}

	// Handle special case where originating object is not actually part of the context collection
	const inputId = activitypub.helpers.isUri(options.input) ? options.input : options.input.id;
	const inCollection = Array.from(chain).map(p => p.pid).includes(inputId);
	if (!inCollection) {
		chain.add(activitypub.helpers.isUri(options.input) ?
			await parseString(uid, options.input) :
			await parseItem(uid, options.input));
	}

	return chain;
};

async function parseString(uid, item) {
	const { type, id } = await activitypub.helpers.resolveLocalId(item);
	const pid = type === 'post' && id ? id : item;
	const postData = await posts.getPostData(pid);
	if (postData) {
		// Already cached
		return postData;
	}

	// No local copy, fetch from source
	try {
		const object = await activitypub.get('uid', uid, pid);
		winston.verbose(`[activitypub/context] Retrieved ${pid}`);

		return parseItem(uid, object);
	} catch (e) {
		// Unresolvable, either temporarily or permanent, ignore for now.
		winston.verbose(`[activitypub/context] Cannot retrieve ${pid}`);
		return null;
	}
}

async function parseItem(uid, item) {
	const { type, id } = await activitypub.helpers.resolveLocalId(item.id);
	const pid = type === 'post' && id ? id : item.id;
	const postData = await posts.getPostData(pid);
	if (postData) {
		// Already cached
		return postData;
	}

	// Handle activity wrapper
	if (item.type === 'Create') {
		item = item.object;
		if (activitypub.helpers.isUri(item)) {
			return parseString(uid, item);
		}
	} else if (!activitypub._constants.acceptedPostTypes.includes(item.type)) {
		// Not a note, silently skip.
		return null;
	}

	winston.verbose(`[activitypub/context] Parsing ${pid}`);
	return await activitypub.mocks.post(item);
}
