import { and, asc, eq, inArray, isNull, or, sql, sum } from "drizzle-orm";
import {
	budgets,
	categories,
	financialAccounts,
	transactions,
} from "@/db/schema";
import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/shared/lib/accounts/constants";
import { excludeTransactionsFromExcludedAccounts } from "@/shared/lib/accounts/query-filters";
import { db } from "@/shared/lib/db";
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";

const toNumber = (value: string | number | null | undefined) => {
	if (typeof value === "number") return value;
	if (typeof value === "string") {
		const parsed = Number.parseFloat(value);
		return Number.isNaN(parsed) ? 0 : parsed;
	}
	return 0;
};

type BudgetData = {
	id: string;
	amount: number;
	spent: number;
	period: string;
	createdAt: string;
	category: {
		id: string;
		name: string;
		icon: string | null;
	} | null;
};

type CategoryOption = {
	id: string;
	name: string;
	icon: string | null;
};

export async function fetchBudgetsForUser(
	userId: string,
	selectedPeriod: string,
): Promise<{
	budgets: BudgetData[];
	categoriesOptions: CategoryOption[];
}> {
	const adminPayerId = await getAdminPayerId(userId);

	const [budgetRows, categoryRows] = await Promise.all([
		db.query.budgets.findMany({
			where: and(
				eq(budgets.userId, userId),
				eq(budgets.period, selectedPeriod),
			),
			with: {
				category: true,
			},
		}),
		db.query.categories.findMany({
			columns: {
				id: true,
				name: true,
				icon: true,
			},
			where: and(eq(categories.userId, userId), eq(categories.type, "despesa")),
			orderBy: asc(categories.name),
		}),
	]);

	const categoryIds = budgetRows
		.map((budget) => budget.categoryId)
		.filter((id: string | null): id is string => Boolean(id));

	let totalsByCategory = new Map<string, number>();

	if (categoryIds.length > 0 && adminPayerId) {
		const totals = await db
			.select({
				categoryId: transactions.categoryId,
				totalAmount: sum(transactions.amount).as("totalAmount"),
			})
			.from(transactions)
			.leftJoin(
				financialAccounts,
				eq(transactions.accountId, financialAccounts.id),
			)
			.where(
				and(
					eq(transactions.userId, userId),
					eq(transactions.period, selectedPeriod),
					eq(transactions.transactionType, "Despesa"),
					eq(transactions.payerId, adminPayerId),
					inArray(transactions.categoryId, categoryIds),
					or(
						isNull(transactions.note),
						sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
					),
					excludeTransactionsFromExcludedAccounts(),
				),
			)
			.groupBy(transactions.categoryId);

		totalsByCategory = new Map(
			totals.map(
				(row: { categoryId: string | null; totalAmount: string | null }) => [
					row.categoryId ?? "",
					Math.abs(toNumber(row.totalAmount)),
				],
			),
		);
	}

	const budgetList = budgetRows
		.map((budget) => ({
			id: budget.id,
			amount: toNumber(budget.amount),
			spent: totalsByCategory.get(budget.categoryId ?? "") ?? 0,
			period: budget.period,
			createdAt: budget.createdAt.toISOString(),
			category: (() => {
				type Cat = { id: string; name: string; icon: string | null };
				const cat = budget.category as Cat | null | undefined;
				return cat ? { id: cat.id, name: cat.name, icon: cat.icon } : null;
			})(),
		}))
		.sort((a, b) =>
			(a.category?.name ?? "").localeCompare(b.category?.name ?? "", "pt-BR", {
				sensitivity: "base",
			}),
		);

	const categoriesOptions = categoryRows.map((category) => ({
		id: category.id,
		name: category.name,
		icon: category.icon,
	}));

	return { budgets: budgetList, categoriesOptions };
}

export type CategoryBudgetSummary = {
	amount: number;
	spent: number;
};

export async function fetchCategoryBudgetSummary(
	userId: string,
	categoryId: string,
	period: string,
): Promise<CategoryBudgetSummary | null> {
	const [adminPayerId, budget] = await Promise.all([
		getAdminPayerId(userId),
		db.query.budgets.findFirst({
			columns: { amount: true },
			where: and(
				eq(budgets.userId, userId),
				eq(budgets.categoryId, categoryId),
				eq(budgets.period, period),
			),
		}),
	]);

	if (!adminPayerId || !budget) return null;

	const totals = await db
		.select({
			totalAmount: sum(transactions.amount).as("totalAmount"),
		})
		.from(transactions)
		.leftJoin(
			financialAccounts,
			eq(transactions.accountId, financialAccounts.id),
		)
		.where(
			and(
				eq(transactions.userId, userId),
				eq(transactions.period, period),
				eq(transactions.transactionType, "Despesa"),
				eq(transactions.payerId, adminPayerId),
				eq(transactions.categoryId, categoryId),
				or(
					isNull(transactions.note),
					sql`${transactions.note} NOT LIKE ${`${ACCOUNT_AUTO_INVOICE_NOTE_PREFIX}%`}`,
				),
				excludeTransactionsFromExcludedAccounts(),
			),
		);

	return {
		amount: toNumber(budget.amount),
		spent: Math.abs(toNumber(totals[0]?.totalAmount ?? 0)),
	};
}
