import { db } from '$lib/server/db';
import { character, characterScrapeValidation } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
import { exec } from 'node:child_process';
import { promisify } from 'node:util';

const execAsync = promisify(exec);
let isScrapeImportRunning = false;
const EXEC_OPTIONS = {
	cwd: process.cwd(),
	maxBuffer: 50 * 1024 * 1024
};

async function applyCharacterChangeFromScrapeValidation(characterId: string): Promise<boolean> {
	const [scraped] = await db
		.select()
		.from(characterScrapeValidation)
		.where(eq(characterScrapeValidation.id, characterId));

	if (!scraped) {
		return false;
	}

	if (scraped.isDeleted) {
		await db.delete(character).where(eq(character.id, characterId));
		return true;
	}

	await db
		.insert(character)
		.values({
			id: scraped.id,
			name: scraped.name,
			frName: scraped.frName,
			gender: scraped.gender,
			age: scraped.age,
			affiliation: scraped.affiliation,
			frAffiliation: scraped.frAffiliation,
			devilFruitId: scraped.devilFruitId,
			hakiObservation: scraped.hakiObservation,
			hakiArmament: scraped.hakiArmament,
			hakiConqueror: scraped.hakiConqueror,
			bounty: scraped.bounty,
			height: scraped.height,
			origin: scraped.origin,
			frOrigin: scraped.frOrigin,
			firstAppearance: scraped.firstAppearance,
			pictureUrl: scraped.pictureUrl,
			epithets: scraped.epithets,
			frEpithets: scraped.frEpithets,
			status: scraped.status,
			arcId: scraped.arcId,
			url: scraped.url,
			frUrl: scraped.frUrl,
		})
		.onConflictDoUpdate({
			target: character.id,
			set: {
				name: scraped.name,
				frName: scraped.frName,
				gender: scraped.gender,
				age: scraped.age,
				affiliation: scraped.affiliation,
				frAffiliation: scraped.frAffiliation,
				devilFruitId: scraped.devilFruitId,
				hakiObservation: scraped.hakiObservation,
				hakiArmament: scraped.hakiArmament,
				hakiConqueror: scraped.hakiConqueror,
				bounty: scraped.bounty,
				height: scraped.height,
				origin: scraped.origin,
				frOrigin: scraped.frOrigin,
				firstAppearance: scraped.firstAppearance,
				pictureUrl: scraped.pictureUrl,
				epithets: scraped.epithets,
				frEpithets: scraped.frEpithets,
				status: scraped.status,
				arcId: scraped.arcId,
				url: scraped.url,
				frUrl: scraped.frUrl
			}
		});

	return true;
}

export async function load() {
	// Get all characters from both tables
	const currentCharacters = await db.select().from(character);
	const scrapedCharacters = await db.select().from(characterScrapeValidation);

	// Create a map for quick lookup
	const currentCharMap = new Map(currentCharacters.map(c => [c.id, c]));

	// Compare and categorize changes
	const changes: {
		type: 'new' | 'modified' | 'deleted';
		id: string;
		scraped: (typeof scrapedCharacters)[0];
		current?: (typeof currentCharacters)[0];
		differences?: Record<string, { current: any; scraped: any }>;
	}[] = [];

	for (const scraped of scrapedCharacters) {
		const current = currentCharMap.get(scraped.id);

		if (scraped.isDeleted) {
			if (current) {
				changes.push({
					type: 'deleted',
					id: scraped.id,
					scraped,
					current
				});
			}
			continue;
		}

		if (!current) {
			// New character
			changes.push({
				type: 'new',
				id: scraped.id,
				scraped
			});
		} else {
			// Check if different
			const differences: Record<string, { current: any; scraped: any }> = {};
			const fieldsToCompare = [
				'name',
				'frName',
				'gender',
				'age',
				'affiliation',
				'frAffiliation',
				'devilFruitId',
				'hakiObservation',
				'hakiArmament',
				'hakiConqueror',
				'bounty',
				'height',
				'origin',
				'frOrigin',
				'firstAppearance',
				'pictureUrl',
				'epithets',
				'frEpithets',
				'status',
				'arcId',
				'url',
				'frUrl'
			];

			for (const field of fieldsToCompare) {
				const currentValue = current[field as keyof typeof current];
				const scrapedValue = scraped[field as keyof typeof scraped];

				// Deep comparison for JSON fields
				if (JSON.stringify(currentValue) !== JSON.stringify(scrapedValue)) {
					differences[field] = {
						current: currentValue,
						scraped: scrapedValue
					};
				}
			}

			if (Object.keys(differences).length > 0) {
				changes.push({
					type: 'modified',
					id: scraped.id,
					scraped,
					current,
					differences
				});
			}
		}
	}

	const typeOrder: Record<'new' | 'modified' | 'deleted', number> = {
		new: 0,
		modified: 1,
		deleted: 2
	};

	return {
		changes: changes.sort((a, b) => {
			if (a.type !== b.type) {
				return typeOrder[a.type] - typeOrder[b.type];
			}
			return a.id.localeCompare(b.id);
		})
	};
}

export const actions = {
	runScrapeImport: async () => {
		if (isScrapeImportRunning) {
			return {
				success: false,
				message: 'A scrape is already running. Please wait for it to finish.',
				logs: ''
			};
		}

		isScrapeImportRunning = true;
		try {
			const scrapeResult = await execAsync('npm run scrape', EXEC_OPTIONS);
			const importResult = await execAsync('npm run db:import', EXEC_OPTIONS);

			const logs = [
				'=== npm run scrape ===',
				scrapeResult.stdout || '',
				scrapeResult.stderr ? `\n[stderr]\n${scrapeResult.stderr}` : '',
				'\n=== npm run db:import ===',
				importResult.stdout || '',
				importResult.stderr ? `\n[stderr]\n${importResult.stderr}` : ''
			]
				.filter(Boolean)
				.join('\n');

			return {
				success: true,
				message: 'Scrape and import completed successfully',
				logs
			};
		} catch (error) {
			const message = error instanceof Error ? error.message : 'Failed to run scripts';
			const stdout = typeof error === 'object' && error && 'stdout' in error ? String((error as any).stdout || '') : '';
			const stderr = typeof error === 'object' && error && 'stderr' in error ? String((error as any).stderr || '') : '';
			const logs = [stdout, stderr ? `\n[stderr]\n${stderr}` : ''].filter(Boolean).join('\n');
			return {
				success: false,
				message,
				logs
			};
		} finally {
			isScrapeImportRunning = false;
		}
	},

	acceptOne: async ({ request }) => {
		const formData = await request.formData();
		const characterId = formData.get('characterId');

		if (!characterId || typeof characterId !== 'string') {
			return { success: false, message: 'characterId is required' };
		}

		const applied = await applyCharacterChangeFromScrapeValidation(characterId);
		return {
			success: applied,
			message: applied ? 'Character change applied successfully' : 'Character not found in scrape validation table'
		};
	},

	acceptAll: async () => {
		const scrapedCharacters = await db.select().from(characterScrapeValidation);
		let appliedCount = 0;

		for (const scraped of scrapedCharacters) {
			const applied = await applyCharacterChangeFromScrapeValidation(scraped.id);
			if (applied) {
				appliedCount++;
			}
		}

		return {
			success: true,
			appliedCount
		};
	}
};
