import { and, count, eq, gt, like, lt, or, type Database } from "@pkg/db";
import type { Result } from "$lib/core/data.types";
import { user } from "@pkg/db/schema";
import { getError, Logger } from "$lib/core/logger";
import { ERROR_CODES } from "$lib/core/error.codes";
import { UserRoleMap } from "$lib/core/enums";
import { decodeCursor, encodeCursor } from "$lib/core/string.utils";

import {
	getDefaultUsersCursor,
	userModel,
	usersCursorModel,
	type UpdateUserInfoInputModel,
	type UsersCursorModel,
	type UserModel,
} from "./entities";

export class UserRepository {
	private db: Database;

	constructor(db: Database) {
		this.db = db;
	}

	async shouldUserLogin(username: string): Promise<Result<boolean>> {
		try {
			const out = await this.db.query.user.findFirst({
				where: eq(user.username, username),
				columns: { role: true },
			});
			return { data: out?.role === UserRoleMap.AGENT };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to get user",
						userHint: "Please contact support to solve the issue",
						actionable: false,
						detail: "An error is raised while getting user from the database",
					},
					err,
				),
			};
		}
	}

	async setParentId(username: string, parentId: string) {
		try {
			await this.db
				.update(user)
				.set({ parentId: parentId })
				.where(eq(user.username, username))
				.execute();
			return { data: true };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to set parent id",
						userHint: "Please contact support to solve the issue",
						actionable: false,
						detail: "An error is raised while setting parent id in the database",
					},
					err,
				),
			};
		}
	}

	async listAllAgentsIds(): Promise<Result<string[]>> {
		try {
			const out = await this.db.query.user.findMany({
				where: eq(user.role, UserRoleMap.AGENT),
				columns: { id: true },
			});
			return { data: out.map((each) => each.id) };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to get agents ids",
						userHint: "Please contact support for assistance",
						actionable: false,
						detail: "An error is raised while getting agents ids from the database",
					},
					err,
				),
			};
		}
	}

	async getParentId(id: string): Promise<Result<string>> {
		try {
			const out = await this.db.query.user.findFirst({
				where: eq(user.id, id),
				columns: { parentId: true },
			});
			if (!out) {
				return {
					error: getError({
						code: ERROR_CODES.NOT_FOUND_ERROR,
						message: "User not found",
						userHint: "Please check the id and try again",
						actionable: false,
						detail: `User with id ${id} not found`,
					}),
				};
			}
			return { data: out?.parentId ?? undefined };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to get user",
						userHint: "Please contact support to solve the issue",
						actionable: false,
						detail: "An error is raised while getting user from the database",
					},
					err,
				),
			};
		}
	}

	async getUserById(id: string): Promise<Result<UserModel>> {
		try {
			const out = await this.db.query.user.findFirst({
				where: eq(user.id, id),
				columns: {
					id: true,
					name: true,
					username: true,
					parentId: true,
					email: true,
					image: true,
					role: true,
					createdAt: true,
					updatedAt: true,
				},
			});
			const parsed = userModel.safeParse(out);
			if (!parsed.success) {
				return {
					error: getError(
						{
							code: ERROR_CODES.DATABASE_ERROR,
							message: "Failed to get user",
							userHint: "Please contact support to solve the issue",
							actionable: false,
							detail: "error while formatting while getting user by id",
						},
						parsed.error.errors[0],
					),
				};
			}
			return { data: parsed.data };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to get user",
						userHint: "Please contact support to solve the issue",
						actionable: false,
						detail: "An error is raised while getting user from the database",
					},
					err,
				),
			};
		}
	}

	async getUsersIdByEmail(email: string): Promise<Result<string>> {
		try {
			const out = await this.db.query.user.findFirst({
				where: eq(user.email, email),
				columns: { id: true },
			});
			return { data: out?.id };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to get user",
						userHint: "Please contact support to solve the issue",
						actionable: false,
						detail: "An error is raised while getting user from the database",
					},
					err,
				),
			};
		}
	}

	async getUsersIdByName(username: string): Promise<Result<string>> {
		try {
			const out = await this.db.query.user.findFirst({
				where: eq(user.name, username),
				columns: { id: true },
			});
			return { data: out?.id };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to get user",
						userHint: "Please contact support to solve the issue",
						actionable: false,
						detail: "An error is raised while getting user from the database",
					},
					err,
				),
			};
		}
	}

	async getUserBy(getBy: {
		username?: string;
		email?: string;
	}): Promise<Result<UserModel>> {
		try {
			const valid = [];
			if (getBy.username) {
				valid.push(eq(user.username, getBy.username));
				valid.push(eq(user.name, getBy.username));
			}
			if (getBy.email) {
				valid.push(eq(user.email, getBy.email));
			}
			const out = await this.db.query.user.findFirst({
				where: and(...valid),
				columns: this.getCommonSelectFields(),
			});
			const parsed = userModel.safeParse(out);
			if (!parsed.success) {
				return {
					error: getError(
						{
							code: ERROR_CODES.DATABASE_ERROR,
							message: "Failed to get user",
							userHint: "Please contact support to solve the issue",
							actionable: false,
							detail: "error while formatting user while getting by username||email",
						},
						parsed.error.errors[0],
					),
				};
			}
			return { data: parsed.data };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to get user",
						userHint: "Please contact support to solve the issue",
						actionable: false,
						detail: "An error is raised while getting user from the database",
					},
					err,
				),
			};
		}
	}

	async getMyUsersCount(
		requesterId: string,
		meta: { cursor?: string },
	): Promise<Result<number>> {
		const _out = decodeCursor<UsersCursorModel>(
			meta.cursor ?? "",
			usersCursorModel,
		);
		let cursor = _out.data!;
		if (_out.error) {
			cursor = getDefaultUsersCursor();
		}
		const baseCondition = [eq(user.parentId, requesterId)];
		let condition = and(...baseCondition);
		if (cursor.query.length > 0) {
			Logger.info(`Getting users with query : ${cursor.query}`);
			condition = and(
				...baseCondition,
				or(
					like(user.email, cursor.query),
					like(user.username, cursor.query),
				),
			);
		}
		const out = await this.db
			.select({ count: count() })
			.from(user)
			.where(condition)
			.execute();
		return { data: out[0].count };
	}

	private getCommonSelectFields() {
		return {
			id: true,
			name: true,
			username: true,
			email: true,
			role: true,
			createdAt: true,
			updatedAt: true,
		} as const;
	}

	async getMyUsersInfo(
		requesterId: string,
		meta: {
			fetchNext: boolean;
			asc: boolean;
			limit: number;
			cursor?: string;
		},
	) {
		const _out = decodeCursor<UsersCursorModel>(
			meta.cursor ?? "",
			usersCursorModel,
		);
		let cursor = _out.data!;
		if (_out.error) {
			cursor = getDefaultUsersCursor();
		}
		console.log("cursor before : ", cursor);
		const baseConditions = [eq(user.parentId, requesterId)];

		if (meta.fetchNext && cursor.lastItemUsername) {
			baseConditions.push(gt(user.username, cursor.lastItemUsername));
		} else if (!meta.fetchNext && cursor.firstItemUsername) {
			baseConditions.push(lt(user.username, cursor.firstItemUsername));
		}

		let condition = and(...baseConditions);
		if (cursor.query.length > 0) {
			condition = and(
				...baseConditions,
				or(
					like(user.email, cursor.query),
					like(user.username, cursor.query),
				),
			);
		}

		const out = await this.db.query.user.findMany({
			where: condition,
			columns: this.getCommonSelectFields(),
			limit: meta.limit,
			orderBy: (user, { asc, desc }) => [
				meta.fetchNext ? asc(user.username) : desc(user.username),
			],
		});
		const data = [] as UserModel[];
		for (const each of out) {
			const parsed = userModel.safeParse(each);
			if (!parsed.success) {
				return {
					error: getError(
						{
							code: ERROR_CODES.DATABASE_ERROR,
							message: "Could not fetch your users",
							userHint:
								"Try refreshing page or contact us for assistance",
							actionable: false,
							detail: "An error occurred while parsing user data from DB",
						},
						parsed.error.errors[0],
					),
				};
			}
			data.push(parsed.data);
		}
		if (data.length < 1) {
			return {
				data: {
					data: [],
					cursor: encodeCursor<UsersCursorModel>({
						...getDefaultUsersCursor(),
						query: cursor.query,
					}),
				},
			};
		}
		const fun = data[0]?.username ?? "";
		const lun = data[data.length - 1]?.username ?? "";
		const newCursor = encodeCursor<UsersCursorModel>({
			// firstItemUsername: meta.fetchNext ? lun : fun,
			// lastItemUsername: meta.fetchNext ? fun : lun,
			firstItemUsername: fun,
			lastItemUsername: lun,
			query: cursor.query,
		});
		data.sort((a, b) => a.username.localeCompare(b.username));
		return { data: { data, cursor: newCursor } };
	}

	async listChildUsers(parentId: string): Promise<Result<UserModel[]>> {
		try {
			const out = await this.db.query.user.findMany({
				where: eq(user.parentId, parentId),
				columns: this.getCommonSelectFields(),
			});
			const parsed = [] as UserModel[];
			for (const each of out) {
				const _parsed = userModel.safeParse(each);
				if (!_parsed.success) {
					return {
						error: getError(
							{
								code: ERROR_CODES.DATABASE_ERROR,
								message: "Failed to get user",
								userHint:
									"Please contact support to solve the issue",
								actionable: false,
								detail: "error in listchildusers",
							},
							_parsed.error.errors[0],
						),
					};
				}
				parsed.push(_parsed.data);
			}
			return { data: parsed };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to list child users",
						userHint: "Please contact support for assistance",
						actionable: false,
						detail: "An error is raised while getting child users from the database",
					},
					err,
				),
			};
		}
	}

	async doesUserExist(email: string, name: string): Promise<Result<boolean>> {
		try {
			const found = await this.db.query.user.findFirst({
				where: or(eq(user.email, email), eq(user.name, name)),
				columns: { id: true },
			});
			return { data: found && !!found.id };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to check if user exists",
						userHint: "Please contact support to solve the issue",
						actionable: false,
						detail: "An error is raised while checking if user exists in the database",
					},
					err,
				),
			};
		}
	}

	async updateUsersInfo(
		parentId: string,
		info: UpdateUserInfoInputModel,
	): Promise<Result<boolean>> {
		try {
			await this.db
				.update(user)
				.set({ username: info.username, updatedAt: new Date() })
				.where(
					and(eq(user.email, info.email), eq(user.parentId, parentId)),
				)
				.execute();

			return { data: true };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to saving agent's info",
						userHint: "Please contact support for assistance",
						actionable: false,
						detail: "An error is raised while saving agent info in the DB",
					},
					err,
				),
			};
		}
	}

	async updateProfileInfo(
		info: UpdateUserInfoInputModel,
	): Promise<Result<boolean>> {
		try {
			await this.db
				.update(user)
				.set({
					email: info.email,
					username: info.username,
					banned: info.banned ?? false,
					updatedAt: new Date(),
				})
				.where(eq(user.email, info.email))
				.execute();
			return { data: true };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to saving agent's info",
						userHint: "Please contact support for assistance",
						actionable: false,
						detail: "An error is raised while saving agent info in the DB",
					},
					err,
				),
			};
		}
	}
}
