import * as request from 'request-promise-native';
import { Instance } from '../models/entities/instance';
import { Instances } from '../models';
import config from '../config';
import { getNodeinfoLock } from '../misc/app-lock';
import Logger from '../services/logger';

export const logger = new Logger('nodeinfo', 'cyan');

export async function fetchNodeinfo(instance: Instance) {
	const unlock = await getNodeinfoLock(instance.host);

	const _instance = await Instances.findOne({ host: instance.host });
	const now = Date.now();
	if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) {
		unlock();
		return;
	}

	logger.info(`Fetching nodeinfo of ${instance.host} ...`);

	try {
		const wellknown = await request({
			url: 'https://' + instance.host + '/.well-known/nodeinfo',
			proxy: config.proxy,
			timeout: 1000 * 10,
			forever: true,
			headers: {
				'User-Agent': config.userAgent,
				Accept: 'application/json, */*'
			},
			json: true
		}).catch(e => {
			if (e.statusCode === 404) {
				throw 'No nodeinfo provided';
			} else {
				throw e.statusCode || e.message;
			}
		});

		if (wellknown.links == null || !Array.isArray(wellknown.links)) {
			throw 'No wellknown links';
		}

		const links = wellknown.links as any[];

		const lnik1_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0');
		const lnik2_0 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0');
		const lnik2_1 = links.find(link => link.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1');
		const link = lnik2_1 || lnik2_0 || lnik1_0;

		if (link == null) {
			throw 'No nodeinfo link provided';
		}

		const info = await request({
			url: link.href,
			proxy: config.proxy,
			timeout: 1000 * 10,
			forever: true,
			headers: {
				'User-Agent': config.userAgent,
				Accept: 'application/json, */*'
			},
			json: true
		}).catch(e => {
			throw e.statusCode || e.message;
		});

		await Instances.update(instance.id, {
			infoUpdatedAt: new Date(),
			softwareName: info.software.name.toLowerCase(),
			softwareVersion: info.software.version,
			openRegistrations: info.openRegistrations,
			name: info.metadata ? (info.metadata.nodeName || info.metadata.name || null) : null,
			description: info.metadata ? (info.metadata.nodeDescription || info.metadata.description || null) : null,
			maintainerName: info.metadata ? info.metadata.maintainer ? (info.metadata.maintainer.name || null) : null : null,
			maintainerEmail: info.metadata ? info.metadata.maintainer ? (info.metadata.maintainer.email || null) : null : null,
		});

		logger.succ(`Successfuly fetched nodeinfo of ${instance.host}`);
	} catch (e) {
		logger.error(`Failed to fetch nodeinfo of ${instance.host}: ${e}`);

		await Instances.update(instance.id, {
			infoUpdatedAt: new Date(),
		});
	} finally {
		unlock();
	}
}