import express from 'express';
import RequestException from '@/request-exception';
import type { UserAgentInfo } from '@/types/common/user-agent-info';

const FIRMWARE_PATCH_REGION_WIIU_REGEX = /(\d)([JEU])/;

export default function parseUserAgentMiddleware(request: express.Request, response: express.Response, next: express.NextFunction): void {
	const userAgent = request.header('user-agent');

	if (!userAgent) {
		return next(new RequestException('Missing or invalid user agent', 400));
	}

	let result: UserAgentInfo | null = null;

	if (userAgent.startsWith('PBOSU')) {
		result = parseWiiU(userAgent);
	} else if (userAgent.startsWith('PBOS')) {
		result = parse3DS(userAgent);
	}

	if (!result) {
		return next(new RequestException('Missing or invalid user agent', 400));
	}

	request.pid = result.userPID;

	return next();
}

function parseWiiU(userAgent: string): UserAgentInfo | null {
	const parts = userAgent.split('/');

	if (parts.length !== 3) {
		return null;
	}

	const [bossLibraryInfo, userInfo, firmwareVersion] = parts;
	const [bossLibraryName, bossLibraryVersion] = bossLibraryInfo.split('-');
	const [bossLibraryVersionMajor, bossLibraryVersionMinor] = bossLibraryVersion.split('.');
	const [deviceIDHex, userPIDHex, unknown] = userInfo.split('-');
	const [firmwareMajor, firmwareMinor, firmwarePatchAndRegion] = firmwareVersion.split('.');
	const [, firmwarePatch, firmwareRegion] = FIRMWARE_PATCH_REGION_WIIU_REGEX.exec(firmwarePatchAndRegion) || [];

	if (
		bossLibraryName !== 'PBOSU' ||
		bossLibraryVersionMajor !== '4' ||
		bossLibraryVersionMinor !== '0' ||
		firmwareMajor !== '5' ||
		firmwareMinor !== '5' ||
		firmwarePatch !== '6' ||
		!['J', 'U', 'E'].includes(firmwareRegion) ||
		deviceIDHex.length !== 8 ||
		userPIDHex.length !== 8 ||
		unknown.length !== 16
	) {
		return null;
	}

	let deviceID: number;
	let userPID: number;

	try {
		deviceID = parseInt(deviceIDHex, 16);
	} catch {
		return null;
	}

	try {
		userPID = parseInt(userPIDHex, 16);
	} catch {
		return null;
	}

	return {
		deviceID,
		userPID
	};
}

function parse3DS(userAgent: string): UserAgentInfo | null {
	const parts = userAgent.split('/');

	if (parts.length !== 5) {
		return null;
	}

	const [bossLibraryInfo, userInfo, firmwareVersion, unknownPart1, unknownPart2] = parts;
	const [bossLibraryName, bossLibraryVersion] = bossLibraryInfo.split('-');
	const [bossLibraryVersionMajor, bossLibraryVersionMinor] = bossLibraryVersion.split('.');
	const [unknownUserInfo1Hex, unknownUserInfo2Hex] = userInfo.split('-');
	const [firmwareMajor, firmwareMinor, firmwarePatchAndRegion] = firmwareVersion.split('.');

	if (
		bossLibraryName !== 'PBOS' ||
		bossLibraryVersionMajor !== '8' ||
		bossLibraryVersionMinor !== '0' ||
		firmwareMajor !== '11' ||
		firmwareMinor !== '17' ||
		!['0-50J', '0-50U', '0-50E'].includes(firmwarePatchAndRegion) || // TODO - Make this more dynamic?
		unknownPart1 !== '62452' || // TODO - Is this right?
		!unknownPart2 || // TODO - Actually check this
		unknownUserInfo1Hex.length !== 16 ||
		unknownUserInfo2Hex.length !== 16
	) {
		return null;
	}

	let unknownUserInfo1: number;
	let unknownUserInfo2: number;

	// TODO - Parse this data? What is this?
	try {
		unknownUserInfo1 = parseInt(unknownUserInfo1Hex, 16);
	} catch {
		return null;
	}

	try {
		unknownUserInfo2 = parseInt(unknownUserInfo2Hex, 16);
	} catch {
		return null;
	}

	// TODO - What is the upper 4 bytes?
	const localFriendCodeSeed = (unknownUserInfo1 & 0xFFFFFFFF); // * LFCS is the lower 4 bytes

	// TODO - What is the upper 4 bytes?
	const userPID = (unknownUserInfo2 & 0xFFFFFFFF); // * PID is the lower 4 bytes

	return {
		localFriendCodeSeed,
		userPID
	};
}