import { CLOSECODES, Payload, OPCODES } from "../util/Constants";
import WebSocket from "../util/WebSocket";
import {
	ChannelModel,
	checkToken,
	GuildModel,
	Intents,
	MemberDocument,
	MemberModel,
	ReadyEventData,
	UserModel,
	toObject,
	EVENTEnum,
	Config,
} from "@fosscord/server-util";
import { setupListener } from "../listener/listener";
import { IdentifySchema } from "../schema/Identify";
import { Send } from "../util/Send";
// import experiments from "./experiments.json";
const experiments: any = [];
import { check } from "./instanceOf";

// TODO: bot sharding
// TODO: check priviliged intents
// TODO: check if already identified

export async function onIdentify(this: WebSocket, data: Payload) {
	clearTimeout(this.readyTimeout);
	check.call(this, IdentifySchema, data.d);

	const identify: IdentifySchema = data.d;

	try {
		const { jwtSecret } = Config.get().security;
		var { decoded } = await checkToken(identify.token, jwtSecret); // will throw an error if invalid
	} catch (error) {
		console.error("invalid token", error);
		return this.close(CLOSECODES.Authentication_failed);
	}
	this.user_id = decoded.id;
	if (!identify.intents) identify.intents = 0b11111111111111n;
	this.intents = new Intents(identify.intents);
	if (identify.shard) {
		this.shard_id = identify.shard[0];
		this.shard_count = identify.shard[1];
		if (
			!this.shard_count ||
			!this.shard_id ||
			this.shard_id >= this.shard_count ||
			this.shard_id < 0 ||
			this.shard_count <= 0
		) {
			return this.close(CLOSECODES.Invalid_shard);
		}
	}

	const members = toObject(await MemberModel.find({ id: this.user_id }).exec());
	const merged_members = members.map((x: any) => {
		const y = { ...x, user_id: x.id };
		delete y.settings;
		delete y.id;
		return [y];
	}) as MemberDocument[][];
	const user_guild_settings_entries = members.map((x) => x.settings);

	const channels = await ChannelModel.find({ recipient_ids: this.user_id }).exec();
	const user = await UserModel.findOne({ id: this.user_id }).exec();
	if (!user) return this.close(CLOSECODES.Authentication_failed);

	const public_user = {
		username: user.username,
		discriminator: user.discriminator,
		id: user.id,
		public_flags: user.public_flags,
		avatar: user.avatar,
		bot: user.bot,
	};

	const guilds = await GuildModel.find({ id: { $in: user.guilds } })
		.populate({ path: "joined_at", match: { id: this.user_id } })
		.exec();

	const privateUser = {
		avatar: user.avatar,
		mobile: user.mobile,
		desktop: user.desktop,
		discriminator: user.discriminator,
		email: user.email,
		flags: user.flags,
		id: user.id,
		mfa_enabled: user.mfa_enabled,
		nsfw_allowed: user.nsfw_allowed,
		phone: user.phone,
		premium: user.premium,
		premium_type: user.premium_type,
		public_flags: user.public_flags,
		username: user.username,
		verified: user.verified,
		bot: user.bot,
		accent_color: user.accent_color || 0,
		banner: user.banner,
	};

	const d: ReadyEventData = {
		v: 8,
		user: privateUser,
		user_settings: user.user_settings,
		// @ts-ignore
		guilds: toObject(guilds).map((x) => {
			// @ts-ignore
			x.guild_hashes = {
				channels: { omitted: false, hash: "y4PV2fZ0gmo" },
				metadata: { omitted: false, hash: "bs1/ckvud3Y" },
				roles: { omitted: false, hash: "SxA+c5CaYpo" },
				version: 1,
			};
			return x;
		}),
		guild_experiments: [], // TODO
		geo_ordered_rtc_regions: [], // TODO
		relationships: user.user_data.relationships,
		read_state: {
			// TODO
			entries: [],
			partial: false,
			version: 304128,
		},
		user_guild_settings: {
			entries: user_guild_settings_entries,
			partial: false, // TODO partial
			version: 642,
		},
		// @ts-ignore
		private_channels: toObject(channels).map((x: ChannelDocument) => {
			x.recipient_ids = x.recipients.map((y: any) => y.id);
			delete x.recipients;
			return x;
		}),
		session_id: "", // TODO
		analytics_token: "", // TODO
		connected_accounts: [], // TODO
		consents: {
			personalization: {
				consented: false, // TODO
			},
		},
		country_code: user.user_settings.locale,
		friend_suggestion_count: 0, // TODO
		// @ts-ignore
		experiments: experiments, // TODO
		guild_join_requests: [], // TODO what is this?
		users: [
			public_user,
			...toObject(channels)
				.map((x: any) => x.recipients)
				.flat(),
		].unique(), // TODO
		merged_members: merged_members,
		// shard // TODO: only for bots sharding
		// application // TODO for applications
	};

	console.log("Send ready");

	// TODO: send real proper data structure
	await Send(this, {
		op: OPCODES.Dispatch,
		t: EVENTEnum.Ready,
		s: this.sequence++,
		d,
	});

	await setupListener.call(this);
}
