import { db } from '$lib/server/db';
import { dailyCompletions, dailyVerses } from '$lib/server/db/schema';
import { eq, desc } from 'drizzle-orm';
import type { PageServerLoad } from './$types';
import { bibleBooks } from '$lib/types/bible';
import { calculateMilestones } from '$lib/server/milestones';
import type { Milestone } from '$lib/server/milestones';

export type BookTier = 'unseen' | 'explored' | 'mastered' | 'perfect';

export type BookGridEntry = {
	bookId: string;
	tier: BookTier;
	avgGuesses: number | null;
	count: number;
};

export type ChartPoint = {
	label: string;
	avgGuesses: number;
};

export type SectionStat = {
	section: string;
	avgGuesses: number;
	count: number;
};

export type TestamentStat = {
	avgGuesses: number;
	count: number;
} | null;

export type { Milestone };

export type ProgressData = {
	completions: Array<{ date: string; guessCount: number }>;
	chartPoints: ChartPoint[];
	bookGrid: BookGridEntry[];
	sectionStats: SectionStat[];
	testamentStats: { old: TestamentStat; new: TestamentStat };
	totalSolves: number;
	bestStreak: number;
	currentStreak: number;
	booksExplored: number;
	booksMastered: number;
	booksPerfect: number;
	bestSingleGame: { date: string; bookName: string } | null;
	totalWords: number;
	streakMilestones: { days7: string | null; days14: string | null; days30: string | null };
	milestones: Milestone[];
};

export const load: PageServerLoad = async ({ locals }) => {
	if (!locals.user) {
		return {
			progress: null,
			requiresAuth: true,
			user: null,
			session: null,
		};
	}

	const userId = locals.user.id;

	try {
		const completions = await db
			.select()
			.from(dailyCompletions)
			.where(eq(dailyCompletions.anonymousId, userId))
			.orderBy(desc(dailyCompletions.date));

		if (completions.length === 0) {
			return {
				progress: {
					completions: [],
					chartPoints: [],
					bookGrid: bibleBooks.map(b => ({ bookId: b.id, tier: 'unseen' as BookTier, avgGuesses: null, count: 0 })),
					sectionStats: [],
					testamentStats: { old: null, new: null },
					totalSolves: 0,
					bestStreak: 0,
					currentStreak: 0,
					booksExplored: 0,
					booksMastered: 0,
					booksPerfect: 0,
					bestSingleGame: null,
					totalWords: 0,
					streakMilestones: { days7: null, days14: null, days30: null },
					milestones: [],
				} satisfies ProgressData,
				requiresAuth: false,
				user: locals.user,
				session: locals.session,
			};
		}

		// Map dates to book IDs and verse text via cached daily_verses
		const allVerses = await db.select().from(dailyVerses);
		const dateToBookId = new Map(allVerses.map(v => [v.date, v.bookId]));
		const dateToVerseText = new Map(allVerses.map(v => [v.date, v.verseText]));

		// Total words across all played verses
		let totalWords = 0;
		for (const c of completions) {
			const verseText = dateToVerseText.get(c.date);
			if (verseText) {
				totalWords += verseText.trim().split(/\s+/).length;
			}
		}

		// Per-book stats
		const bookStatsMap = new Map<string, { count: number; totalGuesses: number; everGuessedIn1: boolean }>();
		for (const c of completions) {
			const bookId = dateToBookId.get(c.date);
			if (!bookId) continue;
			const existing = bookStatsMap.get(bookId) ?? { count: 0, totalGuesses: 0, everGuessedIn1: false };
			bookStatsMap.set(bookId, {
				count: existing.count + 1,
				totalGuesses: existing.totalGuesses + c.guessCount,
				everGuessedIn1: existing.everGuessedIn1 || c.guessCount === 1,
			});
		}

		// Book grid (all 66 in canonical order)
		const bookGrid: BookGridEntry[] = bibleBooks.map(book => {
			const stats = bookStatsMap.get(book.id);
			if (!stats) return { bookId: book.id, tier: 'unseen', avgGuesses: null, count: 0 };
			const avgGuesses = stats.totalGuesses / stats.count;
			let tier: BookTier = 'explored';
			if (stats.count >= 2 && avgGuesses <= 3) {
				tier = stats.everGuessedIn1 ? 'perfect' : 'mastered';
			}
			return { bookId: book.id, tier, avgGuesses: Math.round(avgGuesses * 10) / 10, count: stats.count };
		});

		// Section stats
		const sectionMap = new Map<string, { totalGuesses: number; count: number }>();
		for (const c of completions) {
			const bookId = dateToBookId.get(c.date);
			if (!bookId) continue;
			const book = bibleBooks.find(b => b.id === bookId);
			if (!book) continue;
			const existing = sectionMap.get(book.section) ?? { totalGuesses: 0, count: 0 };
			sectionMap.set(book.section, { totalGuesses: existing.totalGuesses + c.guessCount, count: existing.count + 1 });
		}
		const sectionStats: SectionStat[] = Array.from(sectionMap.entries())
			.filter(([, s]) => s.count >= 3)
			.map(([section, s]) => ({ section, avgGuesses: Math.round((s.totalGuesses / s.count) * 10) / 10, count: s.count }))
			.sort((a, b) => a.avgGuesses - b.avgGuesses);

		// Testament stats (only show if ≥5 games per testament)
		let otTotal = 0, otCount = 0, ntTotal = 0, ntCount = 0;
		for (const c of completions) {
			const bookId = dateToBookId.get(c.date);
			if (!bookId) continue;
			const book = bibleBooks.find(b => b.id === bookId);
			if (!book) continue;
			if (book.testament === 'old') { otTotal += c.guessCount; otCount++; }
			else { ntTotal += c.guessCount; ntCount++; }
		}
		const testamentStats = {
			old: otCount >= 5 ? { avgGuesses: Math.round((otTotal / otCount) * 10) / 10, count: otCount } : null,
			new: ntCount >= 5 ? { avgGuesses: Math.round((ntTotal / ntCount) * 10) / 10, count: ntCount } : null,
		};

		// Chart points — monthly averages sorted ascending
		const sortedCompletions = [...completions].sort((a, b) => a.date.localeCompare(b.date));
		const monthMap = new Map<string, { totalGuesses: number; count: number }>();
		for (const c of sortedCompletions) {
			const month = c.date.slice(0, 7); // YYYY-MM
			const existing = monthMap.get(month) ?? { totalGuesses: 0, count: 0 };
			monthMap.set(month, { totalGuesses: existing.totalGuesses + c.guessCount, count: existing.count + 1 });
		}
		let chartPoints: ChartPoint[] = Array.from(monthMap.entries())
			.map(([label, m]) => ({ label, avgGuesses: Math.round((m.totalGuesses / m.count) * 10) / 10 }));

		// Fall back to weekly if fewer than 3 months of data
		if (chartPoints.length < 3 && sortedCompletions.length >= 5) {
			const weekMap = new Map<string, { totalGuesses: number; count: number }>();
			for (const c of sortedCompletions) {
				const d = new Date(c.date + 'T00:00:00Z');
				const year = d.getUTCFullYear();
				const week = getISOWeek(d);
				const key = `${year}-W${String(week).padStart(2, '0')}`;
				const existing = weekMap.get(key) ?? { totalGuesses: 0, count: 0 };
				weekMap.set(key, { totalGuesses: existing.totalGuesses + c.guessCount, count: existing.count + 1 });
			}
			chartPoints = Array.from(weekMap.entries())
				.map(([label, m]) => ({ label, avgGuesses: Math.round((m.totalGuesses / m.count) * 10) / 10 }));
		}

		// Best streak (all-time) + streak milestones
		const sortedDates = completions.map(c => c.date).sort();
		let bestStreak = sortedDates.length > 0 ? 1 : 0;
		let tempStreak = 1;
		const streakMilestones: { days7: string | null; days14: string | null; days30: string | null } = { days7: null, days14: null, days30: null };
		for (let i = 1; i < sortedDates.length; i++) {
			const curr = new Date(sortedDates[i] + 'T00:00:00Z');
			const prev = new Date(sortedDates[i - 1] + 'T00:00:00Z');
			const diff = Math.round((curr.getTime() - prev.getTime()) / 86400000);
			if (diff === 1) { tempStreak++; }
			else { bestStreak = Math.max(bestStreak, tempStreak); tempStreak = 1; }
			if (tempStreak >= 7 && !streakMilestones.days7) streakMilestones.days7 = sortedDates[i];
			if (tempStreak >= 14 && !streakMilestones.days14) streakMilestones.days14 = sortedDates[i];
			if (tempStreak >= 30 && !streakMilestones.days30) streakMilestones.days30 = sortedDates[i];
		}
		bestStreak = Math.max(bestStreak, tempStreak);

		// Server-side current streak estimate (client overrides via /api/streak)
		const userToday = new Date().toISOString().slice(0, 10);
		const yesterday = new Date(new Date(userToday + 'T00:00:00Z').getTime() - 86400000).toISOString().slice(0, 10);
		const lastDate = sortedDates[sortedDates.length - 1] ?? '';
		let currentStreak = 0;
		if (lastDate === userToday || lastDate === yesterday) {
			currentStreak = 1;
			for (let i = sortedDates.length - 2; i >= 0; i--) {
				const curr = new Date(sortedDates[i + 1] + 'T00:00:00Z');
				const prev = new Date(sortedDates[i] + 'T00:00:00Z');
				const diff = Math.round((curr.getTime() - prev.getTime()) / 86400000);
				if (diff === 1) currentStreak++;
				else break;
			}
		}

		// Milestone counts
		const booksExplored = bookStatsMap.size;
		const booksMastered = bookGrid.filter(b => b.tier === 'mastered' || b.tier === 'perfect').length;
		const booksPerfect = bookGrid.filter(b => b.tier === 'perfect').length;

		// Best single game (earliest 1-guess solve)
		let bestSingleGame: { date: string; bookName: string } | null = null;
		for (const c of sortedCompletions) {
			if (c.guessCount === 1) {
				const bookId = dateToBookId.get(c.date);
				const book = bookId ? bibleBooks.find(b => b.id === bookId) : null;
				if (book) {
					bestSingleGame = { date: c.date, bookName: book.name };
					break;
				}
			}
		}

		const milestones = await calculateMilestones(completions, dateToBookId, { bestSingleGame, streakMilestones });

		return {
			progress: {
				completions: completions.map(c => ({ date: c.date, guessCount: c.guessCount })),
				chartPoints,
				bookGrid,
				sectionStats,
				testamentStats,
				totalSolves: completions.length,
				bestStreak,
				currentStreak,
				booksExplored,
				booksMastered,
				booksPerfect,
				bestSingleGame,
				totalWords,
				streakMilestones,
				milestones,
			} satisfies ProgressData,
			requiresAuth: false,
			user: locals.user,
			session: locals.session,
		};

	} catch (error) {
		console.error('Error fetching progress data:', error);
		return {
			progress: null,
			error: 'Failed to load progress data',
			requiresAuth: false,
			user: locals.user,
			session: locals.session,
		};
	}
};

function getISOWeek(d: Date): number {
	const date = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
	const dayNum = date.getUTCDay() || 7;
	date.setUTCDate(date.getUTCDate() + 4 - dayNum);
	const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
	return Math.ceil((((date.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
}
