import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import { Model } from '@nozbe/watermelondb';

import database from '../database';
import log from './helpers/log';
import { random } from './helpers';
import { Encryption } from '../encryption';
import { E2EType, IMessage, IUser, TMessageModel } from '../../definitions';
import sdk from '../services/sdk';
import { E2E_MESSAGE_TYPE, E2E_STATUS, messagesStatus } from '../constants';
import { saveDraftMessage } from './draftMessage';

const changeMessageStatus = async (id: string, status: number, tmid?: string, message?: IMessage) => {
	const db = database.active;
	const msgCollection = db.get('messages');
	const threadMessagesCollection = db.get('thread_messages');
	const successBatch: Model[] = [];
	const messageRecord = await msgCollection.find(id);
	successBatch.push(
		messageRecord.prepareUpdate(m => {
			m.status = status;
			if (message) {
				m.mentions = message.mentions;
				m.channels = message.channels;
			}
		})
	);

	if (tmid) {
		const threadMessageRecord = await threadMessagesCollection.find(id);
		successBatch.push(
			threadMessageRecord.prepareUpdate(tm => {
				tm.status = status;
				if (message) {
					tm.mentions = message.mentions;
					tm.channels = message.channels;
				}
			})
		);
	}

	try {
		await db.write(async () => {
			await db.batch(...successBatch);
		});
	} catch (error) {
		// Do nothing
	}
};

async function sendMessageCall(message: any) {
	const { _id, tmid } = message;
	try {
		// RC 0.60.0
		// @ts-ignore
		const result = await sdk.post('chat.sendMessage', { message });
		if (result.success) {
			// @ts-ignore
			return changeMessageStatus(_id, messagesStatus.SENT, tmid, result.message);
		}
	} catch {
		// do nothing
	}
	return changeMessageStatus(_id, messagesStatus.ERROR, tmid);
}

export async function resendMessage(message: TMessageModel, tmid?: string) {
	const db = database.active;
	try {
		await db.write(async () => {
			await message.update(m => {
				m.status = messagesStatus.TEMP;
			});
		});
		const m = await Encryption.encryptMessage({
			_id: message.id,
			rid: message.subscription ? message.subscription.id : '',
			msg: message.msg,
			...(tmid && { tmid })
		} as IMessage);

		await sendMessageCall(m);
	} catch (e) {
		log(e);
	}
}

export async function sendMessage(
	rid: string,
	msg: string,
	tmid: string | undefined,
	user: Partial<Pick<IUser, 'id' | 'username' | 'name'>>,
	tshow?: boolean
): Promise<void> {
	try {
		const db = database.active;
		const subsCollection = db.get('subscriptions');
		const msgCollection = db.get('messages');
		const threadCollection = db.get('threads');
		const threadMessagesCollection = db.get('thread_messages');
		const messageId = random(17);
		const batch: Model[] = [];

		const message = await Encryption.encryptMessage({
			_id: messageId,
			rid,
			msg,
			tmid,
			tshow
		} as IMessage);

		const messageDate = new Date();
		let tMessageRecord: TMessageModel;

		// If it's replying to a thread
		if (tmid) {
			try {
				// Find thread message header in Messages collection
				tMessageRecord = await msgCollection.find(tmid);
				batch.push(
					tMessageRecord.prepareUpdate(m => {
						m.tlm = messageDate;
						if (m.tcount) {
							m.tcount += 1;
						}
					})
				);

				try {
					// Find thread message header in Threads collection
					await threadCollection.find(tmid);
				} catch (error) {
					// If there's no record, create one
					batch.push(
						threadCollection.prepareCreate(tm => {
							tm._raw = sanitizedRaw({ id: tmid }, threadCollection.schema);
							if (tm.subscription) {
								tm.subscription.id = rid;
							}
							tm.tmid = tmid;
							tm.msg = tMessageRecord.msg;
							tm.ts = tMessageRecord.ts;
							tm._updatedAt = messageDate;
							tm.status = messagesStatus.SENT; // Original message was sent already
							tm.u = tMessageRecord.u;
							tm.t = message.t;
							tm.attachments = tMessageRecord.attachments;
							if (message.t === E2E_MESSAGE_TYPE) {
								tm.e2e = E2E_STATUS.DONE as E2EType;
							}
						})
					);
				}

				// Create the message sent in ThreadMessages collection
				batch.push(
					threadMessagesCollection.prepareCreate(tm => {
						tm._raw = sanitizedRaw({ id: messageId }, threadMessagesCollection.schema);
						if (tm.subscription) {
							tm.subscription.id = rid;
						}
						tm.rid = tmid;
						tm.msg = msg;
						tm.ts = messageDate;
						tm._updatedAt = messageDate;
						tm.status = messagesStatus.TEMP;
						tm.u = {
							_id: user.id || '1',
							username: user.username,
							name: user.name
						};
						tm.t = message.t;
						if (message.t === E2E_MESSAGE_TYPE) {
							tm.e2e = E2E_STATUS.DONE as E2EType;
						}
					})
				);
			} catch (e) {
				log(e);
			}
		}

		// Create the message sent in Messages collection
		batch.push(
			msgCollection.prepareCreate(m => {
				m._raw = sanitizedRaw({ id: messageId }, msgCollection.schema);
				if (m.subscription) {
					m.subscription.id = rid;
				}
				m.msg = msg;
				m.ts = messageDate;
				m._updatedAt = messageDate;
				m.status = messagesStatus.TEMP;
				m.u = {
					_id: user.id || '1',
					username: user.username,
					name: user.name
				};
				if (tmid && tMessageRecord) {
					m.tmid = tmid;
					// m.tlm = messageDate; // I don't think this is necessary... leaving it commented just in case...
					m.tmsg = tMessageRecord.msg;
					m.tshow = tshow;
				}
				m.t = message.t;
				if (message.t === E2E_MESSAGE_TYPE) {
					m.e2e = E2E_STATUS.DONE as E2EType;
				}
			})
		);

		try {
			const room = await subsCollection.find(rid);
			if (room.draftMessage) {
				batch.push(
					room.prepareUpdate(r => {
						r.draftMessage = null;
					})
				);
			}
		} catch (e) {
			// Do nothing
		}

		try {
			await db.write(async () => {
				await db.batch(...batch);
			});
		} catch (e) {
			log(e);
			return;
		}

		await sendMessageCall(message);
		// clear draft message when message is sent and app is in background or closed
		// do not affect the user experience when the app is in the foreground because the hook useAutoSaveDraft will handle it
		saveDraftMessage({ rid, tmid, draftMessage: '' });
	} catch (e) {
		log(e);
	}
}