/**
 * Code for using Google's Perspective API for filters.
 * @author mia-pi-git
 */
import { ProcessManager, Net } from '../../lib';
import * as ConfigLoader from '../config-loader';
import { toID } from '../../sim/dex-data';

// 20m. this is mostly here so we can use Monitor.slow()
const PM_TIMEOUT = 20 * 60 * 1000;
export const ATTRIBUTES = {
	"SEVERE_TOXICITY": {},
	"TOXICITY": {},
	"IDENTITY_ATTACK": {},
	"INSULT": {},
	"PROFANITY": {},
	"THREAT": {},
	"SEXUALLY_EXPLICIT": {},
	"FLIRTATION": {},
};

export interface PerspectiveRequest {
	languages: string[];
	requestedAttributes: AnyObject;
	comment: { text: string };
}

function time() {
	return Math.floor(Math.floor(Date.now() / 1000) / 60);
}

export class Limiter {
	readonly max: number;
	lastTick = time();
	count = 0;
	constructor(max: number) {
		this.max = max;
	}
	shouldRequest() {
		const now = time();
		if (this.lastTick !== now) {
			this.count = 0;
			this.lastTick = now;
		}
		this.count++;
		return this.count < this.max;
	}
}

function isCommon(message: string) {
	message = message.toLowerCase().replace(/\?!\., ;:/g, '');
	return ['gg', 'wp', 'ggwp', 'gl', 'hf', 'glhf', 'hello', 'hi'].includes(message);
}

let throttleTime: number | null = null;
export const limiter = new Limiter(800);
export const PM = new ProcessManager.QueryProcessManager<string, Record<string, number> | null>(
	'abusemonitor-remote', module,
	async text => {
		if (isCommon(text) || !limiter.shouldRequest()) return null;
		if (throttleTime && ((Date.now() - throttleTime) < 10000)) {
			return null;
		}
		if (throttleTime) throttleTime = null;

		const requestData: PerspectiveRequest = {
			// todo - support 'es', 'it', 'pt', 'fr' - use user.language? room.settings.language...?
			languages: ['en'],
			requestedAttributes: ATTRIBUTES,
			comment: { text },
		};
		try {
			const raw = await Net(`https://commentanalyzer.googleapis.com/v1alpha1/comments:analyze`).post({
				query: {
					key: Config.perspectiveKey,
				},
				body: JSON.stringify(requestData),
				headers: {
					'Content-Type': "application/json",
				},
				timeout: 10 * 1000, // 10s
			});
			if (!raw) return null;
			const data = JSON.parse(raw);
			if (data.error) throw new Error(data.message);
			const result: { [k: string]: number } = {};
			for (const k in data.attributeScores) {
				const score = data.attributeScores[k];
				result[k] = score.summaryScore.value;
			}
			return result;
		} catch (e: any) {
			// eslint-disable-next-line require-atomic-updates
			throttleTime = Date.now();
			if (e.message.startsWith('Request timeout') || e.statusCode === 429 || e.code === 'ETIMEDOUT') {
				// request timeout: just ignore this. error on their end not ours.
				// 429: too many requests, we already freeze for 10s above so. not much more we can do
				return null;
			}
			Monitor.crashlog(e, 'A Perspective API request', { request: JSON.stringify(requestData) });
			return null;
		}
	},
	PM_TIMEOUT
);

export class RemoteClassifier {
	static readonly PM = PM;
	static readonly ATTRIBUTES = ATTRIBUTES;
	classify(text: string) {
		if (!Config.perspectiveKey) return Promise.resolve(null);
		return PM.query(text);
	}
	async suggestScore(text: string, data: Record<string, number>) {
		if (!Config.perspectiveKey) return Promise.resolve(null);
		const body: AnyObject = {
			comment: { text },
			attributeScores: {},
		};
		for (const k in data) {
			body.attributeScores[k] = { summaryScore: { value: data[k] } };
		}
		try {
			const raw = await Net(`https://commentanalyzer.googleapis.com/v1alpha1/comments:suggestscore`).post({
				query: {
					key: Config.perspectiveKey,
				},
				body: JSON.stringify(body),
				headers: {
					'Content-Type': "application/json",
				},
				timeout: 10 * 1000, // 10s
			});
			return JSON.parse(raw);
		} catch (e: any) {
			return { error: e.message };
		}
	}
	destroy() {
		return PM.destroy();
	}
	respawn() {
		return PM.respawn();
	}
	spawn(number: number) {
		PM.spawn(number);
	}
	getActiveProcesses() {
		return PM.processes.length;
	}
	static start(processCount: ConfigLoader.SubProcessesConfig) {
		start(processCount);
	}
}

if (!PM.isParentProcess) {
	ConfigLoader.ensureLoaded();
	global.Monitor = {
		crashlog(error: Error, source = 'A remote Artemis child process', details: AnyObject | null = null) {
			const repr = JSON.stringify([error.name, error.message, source, details]);
			process.send!(`THROW\n@!!@${repr}\n${error.stack}`);
		},
		slow(text: string) {
			process.send!(`CALLBACK\nSLOW\n${text}`);
		},
	} as any;
	global.toID = toID;
	process.on('uncaughtException', err => {
		if (Config.crashguard) {
			Monitor.crashlog(err, 'A remote Artemis child process');
		}
	});
	// eslint-disable-next-line no-eval
	PM.startRepl(cmd => eval(cmd));
}

export function start(processCount: ConfigLoader.SubProcessesConfig) {
	PM.spawn(processCount['remoteartemis'] ?? 1);
}
