import { findClosestSnapshot, getLatestSnapshot, hasAtLeast2Snapshots } from "db/snapshotSchedule";
import { truncate } from "utils/truncate";
import { closetMilestone } from "./exec/snapshotTick";
import { HOUR, MINUTE } from "@core/lib";
import type { Psql } from "@core/db/psql.d";
import { updateETA } from "db/eta";

const log = (value: number, base: number = 10) => Math.log(value) / Math.log(base);

const getFactor = (x: number) => {
	const a = 1.054;
	const b = 4.5;
	const c = 100;
	const u = 0.601;
	const g = 455;
	if (x > g) {
		return log(b / log(x + 1), a);
	} else {
		return log(b / log(x + c), a) + u;
	}
};

/*
 * Returns the minimum ETA in hours for the next snapshot
 * @param client - Postgres client
 * @param aid - aid of the video
 * @returns ETA in hours
 */
export const getAdjustedShortTermETA = async (sql: Psql, aid: number) => {
	const latestSnapshot = await getLatestSnapshot(sql, aid);
	// Immediately dispatch a snapshot if there is no snapshot yet
	if (!latestSnapshot) return 0;
	const snapshotsEnough = await hasAtLeast2Snapshots(sql, aid);
	if (!snapshotsEnough) return 0;

	const currentTimestamp = new Date().getTime();
	const timeIntervals = [3 * MINUTE, 20 * MINUTE, HOUR, 3 * HOUR, 6 * HOUR, 72 * HOUR];
	const DELTA = 0.00001;
	let minETAHours = Infinity;
	let maxSpeed = 0;

	const target = closetMilestone(latestSnapshot.views);
	const viewsToIncrease = target - latestSnapshot.views;
	const factor = truncate(getFactor(viewsToIncrease), 4.5, 100);

	for (const timeInterval of timeIntervals) {
		const date = new Date(currentTimestamp - timeInterval);
		const snapshot = await findClosestSnapshot(sql, aid, date);
		if (!snapshot) continue;
		const hoursDiff = (latestSnapshot.created_at - snapshot.created_at) / HOUR;
		const viewsDiff = latestSnapshot.views - snapshot.views;
		if (viewsDiff <= 0) continue;
		const speed = viewsDiff / (hoursDiff + DELTA);
		const eta = viewsToIncrease / (speed + DELTA);
		const adjustedETA = eta / factor;
		if (adjustedETA < minETAHours) {
			maxSpeed = speed;
			minETAHours = adjustedETA;
		}
	}

	if (isNaN(minETAHours)) {
		minETAHours = Infinity;
	}

	const remaining = closetMilestone(latestSnapshot.views, true) - latestSnapshot.views;
	const avgETAHours = remaining / (maxSpeed + DELTA);

	await updateETA(sql, aid, avgETAHours, maxSpeed, latestSnapshot.views);

	return minETAHours;
};
