import * as faroe_user_server from "@faroe/user-server";
import * as drizzle from "drizzle-orm";
import * as mysql from "drizzle-orm/mysql-core";

const userTable = mysql.mysqlTable("user", {
	// TODO: update column type, set collation to be case sensitive
	id: mysql.varchar("id").primaryKey(), 
	// TODO: set collation to be case sensitive
	emailAddress: mysql.varchar("email_address", { length: 100 }).unique().notNull(),
	passwordHash: mysql.varbinary("password_hash", { length: 255 }).notNull(),
	passwordSalt: mysql.varbinary("password_salt", { length: 255 }).notNull(),
	passwordHashAlgorithmId: mysql.varchar("password_hash_algorithm_id", { length: 127 }).notNull(),
	disabled: mysql.boolean("disabled").notNull().default(false),
	emailAddressCounter: mysql.int("email_address_counter").notNull().default(0),
	passwordHashCounter: mysql.int("password_hash_counter").notNull().default(0),
	disabledCounter: mysql.int("disabled_counter").notNull().default(0),
	sessionsCounter: mysql.int("sessions_counter").notNull().default(0)
});

const actions: faroe_user_server.Actions = {
	async createUserAction(_actionInvocationId, emailAddress, passwordHash, passwordHashAlgorithmId, passwordSalt) {
		const id = generateId(); // TODO
		try {
			await db.insert(userTable).values({
				id,
				emailAddress,
				passwordHash,
				passwordSalt,
				passwordHashAlgorithmId,
			});
		} catch (error) {
			// TODO
			if (error is uniqueConstraintFailedError) {
				throw new faroe_user_server.ActionError("email_address_already_used");
			}
			throw new faroe_user_server.ActionError("unexpected_error");
		}

		const user: faroe_user_server.User = {
			id,
			emailAddress,
			passwordHash,
			passwordHashAlgorithmId,
			passwordSalt,
			disabled: false,
			displayName: emailAddress,
			emailAddressCounter: 0,
			passwordHashCounter: 0,
			disabledCounter: 0,
			sessionsCounter: 0,
		};
		return user;
	},

	async getUserAction(_actionInvocationId, userId) {
		let storedUsers: []drizzle.InferModel<typeof userTable>
		try {
			storedUsers = await db
				.select()
				.from(userTable)
				.where(drizzle.eq(userTable.id, userId))
		} catch {
			throw new faroe_user_server.ActionError("unexpected_error");
		}

		if (storedUsers.length === 0) {
			throw new faroe_user_server.ActionError("user_not_found");
		}

		const user: faroe_user_server.User = {
			id: storedUsers[0].id,
			emailAddress: storedUsers[0].emailAddress,
			passwordHash: storedUsers[0].passwordHash,
			passwordHashAlgorithmId: storedUsers[0].passwordHashAlgorithmId,
			passwordSalt: storedUsers[0].passwordSalt,
			disabled: storedUsers[0].disabled,
			displayName: storedUsers[0].emailAddress,
			emailAddressCounter: storedUsers[0].emailAddressCounter,
			passwordHashCounter: storedUsers[0].passwordHashCounter,
			disabledCounter: storedUsers[0].disabledCounter,
			sessionsCounter: storedUsers[0].sessionsCounter,
		};
		return user;
	},

	async getUserByEmailAddressAction(_actionInvocationId, emailAddress) {
		let storedUsers: []drizzle.InferModel<typeof userTable>
		try {
			storedUsers = await db
				.select()
				.from(userTable)
				.where(drizzle.eq(userTable.emailAddress, emailAddress))
		} catch {
			throw new faroe_user_server.ActionError("unexpected_error");
		}

		if (storedUsers.length === 0) {
			throw new faroe_user_server.ActionError("user_not_found");
		}

		const user: faroe_user_server.User = {
			id: storedUsers[0].id,
			emailAddress: storedUsers[0].emailAddress,
			passwordHash: storedUsers[0].passwordHash,
			passwordHashAlgorithmId: storedUsers[0].passwordHashAlgorithmId,
			passwordSalt: storedUsers[0].passwordSalt,
			disabled: storedUsers[0].disabled,
			displayName: storedUsers[0].emailAddress,
			emailAddressCounter: storedUsers[0].emailAddressCounter,
			passwordHashCounter: storedUsers[0].passwordHashCounter,
			disabledCounter: storedUsers[0].disabledCounter,
			sessionsCounter: storedUsers[0].sessionsCounter,
		};
		return user;
	},

	async updateUserEmailAddressAction(_actionInvocationId, userId, emailAddress, userEmailAddressCounter) {
		let result: { changes: number };
		try {
			result = await db
				.update(userTable)
				.set({
					emailAddress,
					emailAddressCounter: drizzle.sql`${userTable.emailAddressCounter} + 1`,
				})
				.where(
					drizzle.and(
						drizzle.eq(userTable.id, userId),
						drizzle.eq(userTable.emailAddressCounter, userEmailAddressCounter)
					)
				)
		} catch {
			throw new faroe_user_server.ActionError("unexpected_error");
		}

		if (result.changes === 0) {
			throw new faroe_user_server.ActionError("user_not_found");
		}
	},

	async updateUserPasswordHashAction(_actionInvocationId, userId, passwordHash, passwordHashAlgorithmId, passwordSalt, userPasswordHashCounter) {
		let result: { changes: number };
		try {
			result = await db
				.update(userTable)
				.set({
					passwordHash,
					passwordHashAlgorithmId,
					passwordSalt,
					passwordHashCounter: drizzle.sql`${userTable.passwordHashCounter} + 1`,
				})
				.where(
					drizzle.and(
						drizzle.eq(userTable.id, userId),
						drizzle.eq(userTable.passwordHashCounter, userPasswordHashCounter)
					)
				)
		} catch (error) {
			throw new faroe_user_server.ActionError("unexpected_error");
		}

		if (result.changes === 0) {
			throw new faroe_user_server.UserNotFoundError();
		}
	},

	async incrementUserSessionsCounterAction(_actionInvocationId, userId, userSessionsCounter) {
		let result: { changes: number };
		try {
			result = await db
				.update(userTable)
				.set({
					sessionsCounter: drizzle.sql`${userTable.sessionsCounter} + 1`,
				})
				.where(
					drizzle.and(
						drizzle.eq(userTable.id, userId),
						drizzle.eq(userTable.sessionsCounter, userSessionsCounter)
					)
				)
		} catch {
			throw new faroe_user_server.ActionError("unexpected_error");
		}

		if (result.changes === 0) {
			throw new faroe_user_server.ActionError("user_not_found");
		}
	},

	async deleteUserAction(_actionInvocationId, userId) {
		let result: { changes: number };
		try {
			result = await db
				.delete(userTable)
				.where(drizzle.eq(userTable.id, userId))
		} catch (error) {
			throw new faroe_user_server.ActionError("unexpected_error");
		}

		if (result.changes === 0) {
			throw new faroe_user_server.ActionError("user_not_found");
		}
	},
};