import config from "@/config/index.js";
import define from "../../define.js";
import { Instances } from "@/models/index.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
import { sqlLikeEscape } from "@/misc/sql-like-escape.js";

export const meta = {
	tags: ["federation"],

	requireCredential: false,
	requireCredentialPrivateMode: true,

	res: {
		type: "array",
		optional: false,
		nullable: false,
		items: {
			type: "object",
			optional: false,
			nullable: false,
			ref: "FederationInstance",
		},
	},
} as const;

export const paramDef = {
	type: "object",
	properties: {
		host: {
			type: "string",
			nullable: true,
			description: "Omit or use `null` to not filter by host.",
		},
		blocked: { type: "boolean", nullable: true },
		notResponding: { type: "boolean", nullable: true },
		suspended: { type: "boolean", nullable: true },
		federating: { type: "boolean", nullable: true },
		silenced: { type: "boolean", nullable: true },
		subscribing: { type: "boolean", nullable: true },
		publishing: { type: "boolean", nullable: true },
		limit: { type: "integer", minimum: 1, maximum: 100, default: 30 },
		offset: { type: "integer", default: 0 },
		sort: { type: "string" },
	},
	required: [],
} as const;

export default define(meta, paramDef, async (ps, me) => {
	if (!me) {
		ps.blocked = false;
		ps.suspended = false;
		ps.silenced = false;
	}

	const query = Instances.createQueryBuilder("instance");

	switch (ps.sort) {
		case "+pubSub":
			query
				.orderBy("instance.followingCount", "DESC")
				.orderBy("instance.followersCount", "DESC");
			break;
		case "-pubSub":
			query
				.orderBy("instance.followingCount", "ASC")
				.orderBy("instance.followersCount", "ASC");
			break;
		case "+notes":
			query.orderBy("instance.notesCount", "DESC");
			break;
		case "-notes":
			query.orderBy("instance.notesCount", "ASC");
			break;
		case "+users":
			query.orderBy("instance.usersCount", "DESC");
			break;
		case "-users":
			query.orderBy("instance.usersCount", "ASC");
			break;
		case "+following":
			query.orderBy("instance.followingCount", "DESC");
			break;
		case "-following":
			query.orderBy("instance.followingCount", "ASC");
			break;
		case "+followers":
			query.orderBy("instance.followersCount", "DESC");
			break;
		case "-followers":
			query.orderBy("instance.followersCount", "ASC");
			break;
		case "+caughtAt":
			query.orderBy("instance.caughtAt", "DESC");
			break;
		case "-caughtAt":
			query.orderBy("instance.caughtAt", "ASC");
			break;
		case "+lastCommunicatedAt":
			query.orderBy("instance.lastCommunicatedAt", "DESC");
			break;
		case "-lastCommunicatedAt":
			query.orderBy("instance.lastCommunicatedAt", "ASC");
			break;

		default:
			query.orderBy("instance.id", "DESC");
			break;
	}

	if (typeof ps.blocked === "boolean") {
		const meta = await fetchMeta(true);
		if (ps.blocked) {
			if (meta.blockedHosts.length === 0) {
				return [];
			}
			query.andWhere("instance.host IN (:...blocks)", {
				blocks: meta.blockedHosts,
			});
		} else if (meta.blockedHosts.length > 0) {
			query.andWhere("instance.host NOT IN (:...blocks)", {
				blocks: meta.blockedHosts,
			});
		}
	}

	if (typeof ps.silenced === "boolean") {
		const meta = await fetchMeta(true);
		if (ps.silenced) {
			if (meta.silencedHosts.length === 0) {
				return [];
			}
			query.andWhere("instance.host IN (:...silences)", {
				silences: meta.silencedHosts,
			});
		} else if (meta.silencedHosts.length > 0) {
			query.andWhere("instance.host NOT IN (:...silences)", {
				silences: meta.silencedHosts,
			});
		}
	}

	if (typeof ps.notResponding === "boolean") {
		if (ps.notResponding) {
			query.andWhere("instance.isNotResponding = TRUE");
		} else {
			query.andWhere("instance.isNotResponding = FALSE");
		}
	}

	if (typeof ps.suspended === "boolean") {
		if (ps.suspended) {
			query.andWhere("instance.isSuspended = TRUE");
		} else {
			query.andWhere("instance.isSuspended = FALSE");
		}
	}

	if (typeof ps.federating === "boolean") {
		if (ps.federating) {
			query.andWhere(
				"((instance.followingCount > 0) OR (instance.followersCount > 0))",
			);
		} else {
			query.andWhere(
				"((instance.followingCount = 0) AND (instance.followersCount = 0))",
			);
		}
	}

	if (typeof ps.subscribing === "boolean") {
		if (ps.subscribing) {
			query.andWhere("instance.followersCount > 0");
		} else {
			query.andWhere("instance.followersCount = 0");
		}
	}

	if (typeof ps.publishing === "boolean") {
		if (ps.publishing) {
			query.andWhere("instance.followingCount > 0");
		} else {
			query.andWhere("instance.followingCount = 0");
		}
	}

	if (ps.host) {
		query.andWhere("instance.host like :host", {
			host: `%${sqlLikeEscape(ps.host.toLowerCase())}%`,
		});
	}

	const instances = await query.take(ps.limit).skip(ps.offset).getMany();

	return await Instances.packMany(instances, me !== null);
});
