import { URL } from "url";
import httpSignature from "@peertube/http-signature";
import config from "@/config/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { toPuny } from "@/misc/convert-host.js";
import DbResolver from "@/remote/activitypub/db-resolver.js";
import { getApId } from "@/remote/activitypub/type.js";
import { shouldBlockInstance } from "@/misc/should-block-instance.js";
import type { IncomingMessage } from "http";
import type { CacheableRemoteUser } from "@/models/entities/user.js";
import type { UserPublickey } from "@/models/entities/user-publickey.js";

export async function hasSignature(req: IncomingMessage): Promise<string> {
	const meta = await fetchMeta();
	const required = meta.secureMode || meta.privateMode;

	try {
		httpSignature.parseRequest(req, { headers: [] });
	} catch (e) {
		if (e instanceof Error && e.name === "MissingHeaderError") {
			return required ? "missing" : "optional";
		}
		return "invalid";
	}
	return required ? "supplied" : "unneeded";
}

export async function checkFetch(req: IncomingMessage): Promise<number> {
	const meta = await fetchMeta();
	if (meta.secureMode || meta.privateMode) {
		let signature;

		try {
			signature = httpSignature.parseRequest(req, { headers: [] });
		} catch (e) {
			return 401;
		}

		const keyId = new URL(signature.keyId);
		const host = toPuny(keyId.hostname);

		if (await shouldBlockInstance(host, meta)) {
			return 403;
		}

		if (
			meta.privateMode &&
			host !== config.host &&
			!meta.allowedHosts.includes(host)
		) {
			return 403;
		}

		const keyIdLower = signature.keyId.toLowerCase();
		if (keyIdLower.startsWith("acct:")) {
			// Old keyId is no longer supported.
			return 401;
		}

		const dbResolver = new DbResolver();

		// HTTP-Signature keyIdを元にDBから取得
		let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);

		// keyIdでわからなければ、resolveしてみる
		if (authUser == null) {
			try {
				keyId.hash = "";
				authUser = await dbResolver.getAuthUserFromApId(
					getApId(keyId.toString()),
				);
			} catch (e) {
				// できなければ駄目
				return 403;
			}
		}

		// publicKey がなくても終了
		if (authUser?.key == null) {
			return 403;
		}

		// もう一回チェック
		if (authUser.user.host !== host) {
			return 403;
		}

		// HTTP-Signatureの検証
		const httpSignatureValidated = httpSignature.verifySignature(
			signature,
			authUser.key.keyPem,
		);

		if (!httpSignatureValidated) {
			return 403;
		}
	}
	return 200;
}

export async function getSignatureUser(
	req: IncomingMessage,
): Promise<{
	user: CacheableRemoteUser;
	key: UserPublickey | null;
} | null> {
	let authUser;
	const meta = await fetchMeta();
	if (meta.secureMode || meta.privateMode) {
		let signature;

		try {
			signature = httpSignature.parseRequest(req, { headers: [] });
		} catch (e) {
			return null;
		}

		const keyId = new URL(signature.keyId);
		const host = toPuny(keyId.hostname);

		if (await shouldBlockInstance(host, meta)) {
			return null;
		}

		if (
			meta.privateMode &&
			host !== config.host &&
			!meta.allowedHosts.includes(host)
		) {
			return null;
		}

		const keyIdLower = signature.keyId.toLowerCase();
		if (keyIdLower.startsWith("acct:")) {
			// Old keyId is no longer supported.
			return null;
		}

		const dbResolver = new DbResolver();

		// HTTP-Signature keyIdを元にDBから取得
		authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);

		// keyIdでわからなければ、resolveしてみる
		if (!authUser) {
			try {
				keyId.hash = "";
				authUser = await dbResolver.getAuthUserFromApId(
					getApId(keyId.toString()),
				);
			} catch {
				// できなければ駄目
				return null;
			}
		}

		// publicKey がなくても終了
		if (!authUser?.key) {
			return null;
		}

		// もう一回チェック
		if (authUser.user.host !== host) {
			return null;
		}

		// HTTP-Signatureの検証
		const httpSignatureValidated = httpSignature.verifySignature(
			signature,
			authUser.key.keyPem,
		);

		if (!httpSignatureValidated) {
			return null;
		}
	}
	return authUser;
}