// calculates org stats for the last 6 finished months
// used to decide which orgs are considered "established"
// you need at least 150 average monthly active players to be considered established
// if you drop below 100 you lose it

import { db } from "~/db/sql";
import { logger } from "~/utils/logger";

interface MonthData {
	year: number;
	month: number;
	label: string;
	startTimestamp: number;
	endTimestamp: number;
}

interface OrgMonthlyData {
	orgId: number;
	orgName: string;
	monthlyParticipants: number[];
	totalUniqueParticipants: number;
	averageMonthlyParticipants: number;
}

function getLastSixFinishedMonths(): MonthData[] {
	const now = new Date();
	const currentYear = now.getFullYear();
	const currentMonth = now.getMonth();

	const months: MonthData[] = [];

	for (let i = 1; i <= 6; i++) {
		const date = new Date(currentYear, currentMonth - i, 1);
		const year = date.getFullYear();
		const month = date.getMonth();

		const startTimestamp = Math.floor(
			new Date(year, month, 1).getTime() / 1000,
		);
		const endTimestamp = Math.floor(
			new Date(year, month + 1, 1).getTime() / 1000,
		);

		months.push({
			year,
			month,
			label: date.toLocaleDateString("en-US", {
				year: "numeric",
				month: "short",
			}),
			startTimestamp,
			endTimestamp,
		});
	}

	return months;
}

async function getParticipantsForOrgInMonth(
	organizationId: number,
	startTimestamp: number,
	endTimestamp: number,
): Promise<number> {
	const result = await db
		.selectFrom("CalendarEvent as ce")
		.innerJoin("CalendarEventDate as ced", "ced.eventId", "ce.id")
		.innerJoin("Tournament as t", "t.id", "ce.tournamentId")
		.innerJoin("TournamentTeam as tt", "tt.tournamentId", "t.id")
		.innerJoin(
			"TournamentTeamCheckIn as ttci",
			"ttci.tournamentTeamId",
			"tt.id",
		)
		.innerJoin(
			"TournamentMatchGameResultParticipant as tmgrp",
			"tmgrp.tournamentTeamId",
			"tt.id",
		)
		.select(({ fn }) => fn.count<number>("tmgrp.userId").distinct().as("count"))
		.where("ce.organizationId", "=", organizationId)
		.where("ced.startTime", ">=", startTimestamp)
		.where("ced.startTime", "<", endTimestamp)
		.where("ttci.checkedInAt", "is not", null)
		.where("ttci.isCheckOut", "=", 0)
		.executeTakeFirst();

	return result?.count ?? 0;
}

async function getOrgsWithRecentTournaments(
	months: MonthData[],
): Promise<number[]> {
	const earliestTimestamp = months[months.length - 1].startTimestamp;

	const orgs = await db
		.selectFrom("CalendarEvent as ce")
		.innerJoin("CalendarEventDate as ced", "ced.eventId", "ce.id")
		.select("ce.organizationId")
		.distinct()
		.where("ce.organizationId", "is not", null)
		.where("ced.startTime", ">=", earliestTimestamp)
		.execute();

	return orgs.map((org) => org.organizationId!);
}

async function main() {
	const months = getLastSixFinishedMonths();
	logger.info(
		`Analyzing last 6 finished months: ${months.map((m) => m.label).join(", ")}\n`,
	);

	const orgIds = await getOrgsWithRecentTournaments(months);
	logger.info(
		`Found ${orgIds.length} organizations with tournaments in this period\n`,
	);

	const orgData: OrgMonthlyData[] = [];

	for (const orgId of orgIds) {
		const org = await db
			.selectFrom("TournamentOrganization")
			.select(["id", "name"])
			.where("id", "=", orgId)
			.executeTakeFirst();

		if (!org) continue;

		const monthlyParticipants: number[] = [];

		for (const month of months) {
			const count = await getParticipantsForOrgInMonth(
				orgId,
				month.startTimestamp,
				month.endTimestamp,
			);
			monthlyParticipants.push(count);
		}

		const totalUniqueParticipants = monthlyParticipants.reduce(
			(sum, count) => sum + count,
			0,
		);
		const averageMonthlyParticipants = totalUniqueParticipants / 6;

		orgData.push({
			orgId: org.id,
			orgName: org.name,
			monthlyParticipants,
			totalUniqueParticipants,
			averageMonthlyParticipants,
		});
	}

	orgData.sort((a, b) => {
		if (b.averageMonthlyParticipants !== a.averageMonthlyParticipants) {
			return b.averageMonthlyParticipants - a.averageMonthlyParticipants;
		}
		return b.totalUniqueParticipants - a.totalUniqueParticipants;
	});

	logger.info("=".repeat(80));
	logger.info("ORGANIZATION MONTHLY ACTIVE PLAYERS REPORT");
	logger.info("=".repeat(80));
	logger.info();

	const headers = ["Org", "Avg", ...months.map((m) => m.label)];
	logger.info(headers.join(" | "));
	logger.info("-".repeat(80));

	for (const org of orgData) {
		const monthValues = org.monthlyParticipants.map((count) =>
			count === 0 ? "-" : count.toString(),
		);
		const row = [
			org.orgName,
			org.averageMonthlyParticipants.toFixed(1),
			...monthValues,
		];
		logger.info(row.join(" | "));
	}

	logger.info();
	logger.info(`Total organizations: ${orgData.length}`);
}

main().catch((err) => {
	logger.error("Error in org-monthly-active-players.ts", err);
	process.exit(1);
});
