import { and, count, countDistinct, gte, lt, sql } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { analytics } from "../db/schema/analytics.schema.js";
import { analyticsDaily } from "../db/schema/dailyAnalytics.schema.js";

export const ignoredRoutePrefixes = [
	"/health",
	"/favicon.ico",
	"/socket.io",
	"/lst/api/ws",
	"/lst-config.js",
];

export function shouldIgnoreRoute(path: string) {
	return ignoredRoutePrefixes.some((prefix) => path.startsWith(prefix));
}

type CreateRouteHitInput = {
	method: string;
	routePattern: string;
	actualPath: string;
	statusCode: number;
	durationMs: number;
	module?: string | null;
	userId?: string | null;
	userEmail?: string | null;
	ipAddress?: string | null;
	userAgent?: string | null;
};

export async function createRouteHit(input: CreateRouteHitInput) {
	await db.insert(analytics).values(input);
}

function getPreviousBusinessDayWindow(date = new Date()) {
	const end = new Date(date);
	end.setHours(7, 0, 0, 0);

	const start = new Date(end);
	start.setDate(start.getDate() - 1);

	const businessDate = start.toISOString().slice(0, 10);

	return {
		start,
		end,
		businessDate,
	};
}

export async function runRouteHitAnalyticsCron(): Promise<void> {
	const result = await aggregateRouteHitsForBusinessDay();

	await cleanupOldRouteHits();

	console.log("Route hit analytics aggregated", result);
}

export async function aggregateRouteHitsForBusinessDay() {
	const { start, end, businessDate } = getPreviousBusinessDayWindow();

	const rows = await db
		.select({
			businessDate: sql<string>`CAST(${businessDate} AS date)`,
			method: analytics.method,
			routePattern: analytics.routePattern,
			module: sql<string>`COALESCE(${analytics.module}, 'unknown')`,

			totalHits: count(),
			uniqueUsers: countDistinct(analytics.userId),

			successCount: sql<number>`
			COUNT(*) FILTER (WHERE ${analytics.statusCode} < 400)
		`,
			errorCount: sql<number>`
			COUNT(*) FILTER (WHERE ${analytics.statusCode} >= 400)
		`,

			avgDurationMs: sql<number>`
			COALESCE(ROUND(AVG(${analytics.durationMs})), 0)
		`,
			maxDurationMs: sql<number>`
			COALESCE(MAX(${analytics.durationMs}), 0)
		`,

			firstHitAt: sql<Date>`
			COALESCE(MIN(${analytics.createdAt}), NOW())
		`,
			lastHitAt: sql<Date>`
			COALESCE(MAX(${analytics.createdAt}), NOW())
		`,
		})
		.from(analytics)
		.where(and(gte(analytics.createdAt, start), lt(analytics.createdAt, end)))
		.groupBy(
			analytics.method,
			analytics.routePattern,
			sql`COALESCE(${analytics.module}, 'unknown')`,
		);

	if (rows.length === 0) {
		return {
			businessDate,
			inserted: 0,
		};
	}

	const values = rows.map((row) => ({
		...row,
		businessDate: row.businessDate,
		firstHitAt: new Date(row.firstHitAt),
		lastHitAt: new Date(row.lastHitAt),
	}));

	await db
		.insert(analyticsDaily)
		.values(values)
		.onConflictDoUpdate({
			target: [
				analyticsDaily.businessDate,
				analyticsDaily.method,
				analyticsDaily.routePattern,
				analyticsDaily.module,
			],
			set: {
				totalHits: sql`excluded.total_hits`,
				uniqueUsers: sql`excluded.unique_users`,
				successCount: sql`excluded.success_count`,
				errorCount: sql`excluded.error_count`,
				avgDurationMs: sql`excluded.avg_duration_ms`,
				maxDurationMs: sql`excluded.max_duration_ms`,
				firstHitAt: sql`excluded.first_hit_at`,
				lastHitAt: sql`excluded.last_hit_at`,
				updatedAt: sql`now()`,
			},
		});

	return {
		businessDate,
		inserted: rows.length,
	};
}

export async function cleanupOldRouteHits() {
	await db
		.delete(analytics)
		.where(lt(analytics.createdAt, sql`now() - interval '4 days'`));
}
