import { db } from '$lib/server/db';
import { dailyCompletions } from '$lib/server/db/schema';
import { bibleBooks } from '$lib/types/bible';
import { inArray } from 'drizzle-orm';
import type { DailyCompletion } from '$lib/server/db/schema';

export type Milestone = {
	id: string;
	name: string;
	emoji: string;
	description: string;
	achieved: boolean;
	achievedDate: string | null; // YYYY-MM-DD of first achievement, or null
};

export type ClassicMilestoneInputs = {
	bestSingleGame: { date: string; bookName: string } | null;
	streakMilestones: { days7: string | null; days14: string | null; days30: string | null };
};

export async function calculateMilestones(
	completions: DailyCompletion[],
	dateToBookId: Map<string, string>,
	classic: ClassicMilestoneInputs,
): Promise<Milestone[]> {
	const sorted = [...completions].sort((a, b) => a.date.localeCompare(b.date));

	// Helper: returns the date when all books in targetIds were first solved
	function findSetDate(targetIds: Set<string>): string | null {
		const solved = new Set<string>();
		for (const c of sorted) {
			const bookId = dateToBookId.get(c.date);
			if (bookId && targetIds.has(bookId)) {
				solved.add(bookId);
				if (solved.size === targetIds.size) return c.date;
			}
		}
		return null;
	}

	// Book sets
	const ntIds = new Set(bibleBooks.filter(b => b.testament === 'new').map(b => b.id));
	const otIds = new Set(bibleBooks.filter(b => b.testament === 'old').map(b => b.id));
	const allIds = new Set(bibleBooks.map(b => b.id));
	const gospelIds = new Set(['MAT', 'MRK', 'LUK', 'JHN']);
	const pentateuchIds = new Set(['GEN', 'EXO', 'LEV', 'NUM', 'DEU']);

	// Set-completion milestones
	const ntScholarDate = findSetDate(ntIds);
	const otScholarDate = findSetDate(otIds);
	const theologianDate = findSetDate(allIds);
	const fantasticFourDate = findSetDate(gospelIds);
	const pentatonixDate = findSetDate(pentateuchIds);

	// With God, All Things Are Possible — solved in 1 guess for at least one puzzle from each of the 66 books
	const booksInOne = new Set<string>();
	let withGodDate: string | null = null;
	for (const c of sorted) {
		if (c.guessCount === 1) {
			const bookId = dateToBookId.get(c.date);
			if (bookId) {
				booksInOne.add(bookId);
				if (withGodDate === null && booksInOne.size === allIds.size) {
					withGodDate = c.date;
				}
			}
		}
	}
	const allInOne = booksInOne.size === allIds.size;

	// Is This A Joke To You? — guessed all 65 other books first (66 guesses total)
	const jokeCompletion = sorted.find(c => c.guessCount >= 66);

	// Prodigal Son — returned after a 30+ day gap
	let prodigalDate: string | null = null;
	for (let i = 1; i < sorted.length; i++) {
		const prev = new Date(sorted[i - 1].date + 'T00:00:00Z');
		const curr = new Date(sorted[i].date + 'T00:00:00Z');
		const diff = Math.round((curr.getTime() - prev.getTime()) / 86400000);
		if (diff >= 30) {
			prodigalDate = sorted[i].date;
			break;
		}
	}

	// Extra Credit — solved on a Sunday
	const sundayCompletion = sorted.find(c => {
		const d = new Date(c.date + 'T00:00:00Z');
		return d.getUTCDay() === 0;
	});

	// Cross-user milestones: Overachiever, Procrastinator, Outlier
	let overachieverDate: string | null = null;
	let procrastinatorDate: string | null = null;
	let outlierDate: string | null = null;

	if (sorted.length > 0) {
		const userDates = sorted.map(c => c.date);
		const allOnDates = await db
			.select({
				date: dailyCompletions.date,
				completedAt: dailyCompletions.completedAt,
				guessCount: dailyCompletions.guessCount,
				anonymousId: dailyCompletions.anonymousId,
			})
			.from(dailyCompletions)
			.where(inArray(dailyCompletions.date, userDates));

		// Group all completions by date
		const byDate = new Map<string, typeof allOnDates>();
		for (const c of allOnDates) {
			const arr = byDate.get(c.date) ?? [];
			arr.push(c);
			byDate.set(c.date, arr);
		}

		const userByDate = new Map(sorted.map(c => [c.date, c]));

		for (const userComp of sorted) {
			const allForDate = byDate.get(userComp.date) ?? [];
			if (allForDate.length < 2) continue; // need multiple players

			const validTimes = allForDate
				.filter(c => c.completedAt != null)
				.map(c => c.completedAt!.getTime());

			if (!overachieverDate && userComp.completedAt && validTimes.length > 0) {
				const earliest = Math.min(...validTimes);
				if (userComp.completedAt.getTime() === earliest) {
					overachieverDate = userComp.date;
				}
			}

			if (!procrastinatorDate && userComp.completedAt && validTimes.length > 0) {
				const latest = Math.max(...validTimes);
				if (userComp.completedAt.getTime() === latest) {
					procrastinatorDate = userComp.date;
				}
			}

			if (!outlierDate && allForDate.length >= 10) {
				const sortedGuesses = allForDate.map(c => c.guessCount).sort((a, b) => a - b);
				const cutoffIndex = Math.ceil(sortedGuesses.length * 0.1) - 1;
				const cutoff = sortedGuesses[cutoffIndex];
				if (userComp.guessCount <= cutoff) {
					outlierDate = userComp.date;
				}
			}
		}
	}

	return [
		{
			id: 'first-1-guess',
			name: 'Lightning Strike',
			emoji: '⚡',
			description: `First 1-guess solve${classic.bestSingleGame ? ` — ${classic.bestSingleGame.bookName}` : ''}`,
			achieved: classic.bestSingleGame !== null,
			achievedDate: classic.bestSingleGame?.date ?? null,
		},
		{
			id: 'streak-7',
			name: '7-Day Streak',
			emoji: '🔥',
			description: 'Solve Bibdle 7 days in a row',
			achieved: classic.streakMilestones.days7 !== null,
			achievedDate: classic.streakMilestones.days7,
		},
		{
			id: 'streak-14',
			name: '14-Day Streak',
			emoji: '💥',
			description: 'Solve Bibdle 14 days in a row',
			achieved: classic.streakMilestones.days14 !== null,
			achievedDate: classic.streakMilestones.days14,
		},
		{
			id: 'streak-30',
			name: '30-Day Streak',
			emoji: '🏅',
			description: 'Solve Bibdle 30 days in a row',
			achieved: classic.streakMilestones.days30 !== null,
			achievedDate: classic.streakMilestones.days30,
		},
		{
			id: 'nt-scholar',
			name: 'NT Scholar',
			emoji: '✝️',
			description: 'Solve for every New Testament book',
			achieved: ntScholarDate !== null,
			achievedDate: ntScholarDate,
		},
		{
			id: 'ot-scholar',
			name: 'OT Scholar',
			emoji: '📜',
			description: 'Solve  for every Old Testament book',
			achieved: otScholarDate !== null,
			achievedDate: otScholarDate,
		},
		{
			id: 'theologian',
			name: 'Theologian',
			emoji: '🎓',
			description: 'Solve for all 66 books of the Bible',
			achieved: theologianDate !== null,
			achievedDate: theologianDate,
		},
		{
			id: 'fantastic-four',
			name: 'The Fantastic Four',
			emoji: '4️⃣',
			description: 'Solve a puzzle for all four Gospels',
			achieved: fantasticFourDate !== null,
			achievedDate: fantasticFourDate,
		},
		{
			id: 'pentatonix',
			name: 'Pentatonix',
			emoji: '📃',
			description: 'Solve a puzzle for all five books of the Pentateuch',
			achieved: pentatonixDate !== null,
			achievedDate: pentatonixDate,
		},
		{
			id: 'with-god',
			name: 'With God, All Things Are Possible',
			emoji: '🙏',
			description: 'Solve in 1 guess for each of the 66 books at least once',
			achieved: allInOne,
			achievedDate: withGodDate,
		},
		{
			id: 'is-this-a-joke',
			name: 'Is This A Joke To You?',
			emoji: '😤',
			description: 'Guess all 65 other books before getting the right one',
			achieved: jokeCompletion !== undefined,
			achievedDate: jokeCompletion?.date ?? null,
		},
		{
			id: 'overachiever',
			name: 'Overachiever',
			emoji: '⚡',
			description: 'Be the first person to solve Bibdle on a day',
			achieved: overachieverDate !== null,
			achievedDate: overachieverDate,
		},
		{
			id: 'procrastinator',
			name: 'Procrastinator',
			emoji: '🐢',
			description: 'Be the last person to solve Bibdle on a day',
			achieved: procrastinatorDate !== null,
			achievedDate: procrastinatorDate,
		},
		{
			id: 'prodigal-son',
			name: 'Prodigal Son',
			emoji: '🏠',
			description: 'Return to Bibdle after at least 30 days away',
			achieved: prodigalDate !== null,
			achievedDate: prodigalDate,
		},
		{
			id: 'extra-credit',
			name: 'Extra Credit',
			emoji: '📅',
			description: 'Solve Bibdle on a Sunday',
			achieved: sundayCompletion !== undefined,
			achievedDate: sundayCompletion?.date ?? null,
		},
		{
			id: 'outlier',
			name: 'Outlier',
			emoji: '📊',
			description: 'Finish in the top 10% of solves on a day (fewest guesses)',
			achieved: outlierDate !== null,
			achievedDate: outlierDate,
		},
	];
}
