function parseJsonBlob(blob) {
	if (!blob) return null;
	try {
		const text = typeof blob === 'string' ? blob : Buffer.from(blob).toString('utf8');
		return JSON.parse(text);
	} catch {
		return null;
	}
}

/**
 * Emby stores GUIDs in SQLite as 16-byte blobs using Microsoft mixed-endian ordering.
 * Components 1-3 are little-endian; components 4-5 are big-endian.
 */
export function blobToEmbyGuid(blob) {
	if (!blob) return '';
	const b = blob instanceof Uint8Array ? blob : new Uint8Array(blob);
	if (b.length !== 16) return Buffer.from(b).toString('hex').toLowerCase();
	const out = new Uint8Array([
		b[3], b[2], b[1], b[0],
		b[5], b[4],
		b[7], b[6],
		b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]
	]);
	return Buffer.from(out).toString('hex').toLowerCase();
}

function hasTable(db, tableName) {
	const row = db
		.prepare("SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ?")
		.get(tableName);
	return !!row?.found;
}

function normalizeSectionUserId(sectionUserId) {
	return typeof sectionUserId === 'string' ? sectionUserId.trim().toLowerCase() : '';
}

export function normalizeSectionsForUser(sections, expectedEmbyGuid) {
	if (!Array.isArray(sections)) return [];
	if (!expectedEmbyGuid) return sections;

	return sections.map((section) => ({
		...section,
		UserId: expectedEmbyGuid
	}));
}

function loadUsersTableUsers(db) {
	const cols = db.prepare('PRAGMA table_info(Users)').all().map((c) => c.name);
	const nameCol = cols.find((c) => /^username$/i.test(c)) || cols.find((c) => /^name$/i.test(c));
	const guidCol = cols.find((c) => /^guid$/i.test(c));
	const idCol = cols.find((c) => /^id$/i.test(c)) || 'Id';

	if (!nameCol) {
		throw new Error(`Cannot find a name column in Users table. Columns found: ${cols.join(', ')}`);
	}

	const selectCols = [idCol, nameCol, guidCol].filter(Boolean).join(', ');
	const rows = db.prepare(`SELECT ${selectCols} FROM Users`).all();

	return rows.map((row) => {
		const rawId = row[idCol] ?? row.Id ?? row.id;
		const rawGuid = guidCol ? row[guidCol] : null;
		let embyGuid = '';
		let guid = '';

		if (rawGuid) {
			if (rawGuid instanceof Uint8Array || rawGuid instanceof Buffer) {
				const buf = Buffer.from(rawGuid);
				guid = buf.toString('hex').toUpperCase();
				embyGuid = blobToEmbyGuid(rawGuid);
			} else if (typeof rawGuid === 'string') {
				const clean = rawGuid.replace(/-/g, '').toLowerCase();
				embyGuid = clean;
				guid = clean.toUpperCase();
			}
		}

		return {
			id: rawId,
			name: row[nameCol] || `User ${rawId}`,
			guid,
			embyGuid,
			sourceTable: 'Users'
		};
	});
}

function loadLocalUsers(db) {
	const rows = db.prepare('SELECT Id, guid, data FROM LocalUsersv2').all();

	return rows.map((row) => {
		const parsed = parseJsonBlob(row.data);
		const guid = row.guid ? Buffer.from(row.guid).toString('hex').toUpperCase() : '';
		const embyGuidFromBlob = blobToEmbyGuid(row.guid);
		const embyGuidFromJson = normalizeSectionUserId(parsed?.IdString);
		const embyGuid = embyGuidFromJson || embyGuidFromBlob;

		return {
			id: row.Id,
			name: parsed?.Name || `User ${row.Id}`,
			guid,
			embyGuid,
			sourceTable: 'LocalUsersv2',
			profile: parsed || null
		};
	});
}

export function loadCanonicalUsers(db) {
	if (hasTable(db, 'LocalUsersv2')) {
		return loadLocalUsers(db);
	}
	if (hasTable(db, 'Users')) {
		return loadUsersTableUsers(db);
	}
	throw new Error('No supported user table found. Expected LocalUsersv2 or Users.');
}

export function loadHomeScreenUsers(db) {
	const users = loadCanonicalUsers(db);
	const settingsRows = db
		.prepare(
			`SELECT us.UserId, us.Value
		 FROM UserSettings us
		 JOIN UserSettingsKeys usk ON us.UserSettingsKeyId = usk.UserSettingsKeyId
		 WHERE usk.Name = 'homescreensettings'`
		)
		.all();

	const settingsMap = new Map(settingsRows.map((row) => [String(row.UserId), row.Value]));
	const userIds = new Set(users.map((user) => String(user.id)));

	let matchedUsers = 0;
	let mismatchedUsers = 0;
	let normalizedUsers = 0;
	let missingSectionUserIds = 0;

	const hydratedUsers = users.map((user) => {
		const rawValue = settingsMap.get(String(user.id));
		let sections = [];

		try {
			if (rawValue) sections = JSON.parse(rawValue).Sections || [];
		} catch {
			sections = [];
		}

		const actualUserIds = [...new Set(sections.map((section) => normalizeSectionUserId(section?.UserId)).filter(Boolean))];
		const mismatchedSectionUserIds = user.embyGuid
			? actualUserIds.filter((id) => id !== user.embyGuid)
			: actualUserIds;
		const missingIdsForUser = sections.filter((section) => !normalizeSectionUserId(section?.UserId)).length;
		const normalizedSections = normalizeSectionsForUser(sections, user.embyGuid);
		const sectionsWereNormalized =
			user.embyGuid &&
			JSON.stringify(sections) !== JSON.stringify(normalizedSections);

		if (mismatchedSectionUserIds.length === 0) matchedUsers++;
		else mismatchedUsers++;
		if (sectionsWereNormalized) normalizedUsers++;
		missingSectionUserIds += missingIdsForUser;

		return {
			id: user.id,
			name: user.name,
			guid: user.guid,
			embyGuid: user.embyGuid,
			sections: normalizedSections,
			details: {
				sourceTable: user.sourceTable,
				lastLoginDate: user.profile?.LastLoginDate || null,
				lastActivityDate: user.profile?.LastActivityDate || null,
				usesIdForConfigurationPath: user.profile?.UsesIdForConfigurationPath ?? null,
				importedCollectionsCount: Array.isArray(user.profile?.ImportedCollections) ? user.profile.ImportedCollections.length : 0
			},
			match: {
				sourceTable: user.sourceTable,
				settingsUserId: user.id,
				expectedSectionUserId: user.embyGuid,
				actualSectionUserIds: actualUserIds,
				mismatchedSectionUserIds,
				missingSectionUserIds: missingIdsForUser,
				ok: mismatchedSectionUserIds.length === 0
			}
		};
	});

	const orphanedSettingsUserIds = settingsRows
		.map((row) => String(row.UserId))
		.filter((userId, index, all) => all.indexOf(userId) === index && !userIds.has(userId));

	return {
		users: hydratedUsers,
		validation: {
			userSource: users[0]?.sourceTable || null,
			userCount: hydratedUsers.length,
			settingsCount: settingsRows.length,
			matchedUsers,
			mismatchedUsers,
			normalizedUsers,
			missingSectionUserIds,
			orphanedSettingsUserIds
		}
	};
}

export function loadUserLookup(db) {
	return new Map(loadCanonicalUsers(db).map((user) => [String(user.id), user]));
}
