import { and, desc, eq } from "drizzle-orm";
import {
	cards,
	categories,
	financialAccounts,
	transactions,
} from "@/db/schema";
import type { DashboardBillsSnapshot } from "@/features/dashboard/bills/bills-queries";
import type { PurchasesByCategoryData } from "@/features/dashboard/categories/purchases-by-category-queries";
import type { InstallmentExpensesData } from "@/features/dashboard/expenses/installment-expenses-queries";
import type { RecurringExpensesData } from "@/features/dashboard/expenses/recurring-expenses-queries";
import type {
	TopExpense,
	TopExpensesData,
} from "@/features/dashboard/expenses/top-expenses-queries";
import type { TopEstablishmentsData } from "@/features/dashboard/lib/top-establishments-queries";
import { excludeTransactionsFromExcludedAccounts } from "@/features/dashboard/lib/transaction-filters";
import type { PaymentConditionsData } from "@/features/dashboard/payments/payment-conditions-queries";
import type { PaymentMethodsData } from "@/features/dashboard/payments/payment-methods-queries";
import type { PaymentStatusData } from "@/features/dashboard/payments/payment-status-queries";
import {
	ACCOUNT_AUTO_INVOICE_NOTE_PREFIX,
	INITIAL_BALANCE_NOTE,
	isRefundNote,
} from "@/shared/lib/accounts/constants";
import { db } from "@/shared/lib/db";
import { getAdminPayerId } from "@/shared/lib/payers/get-admin-id";
import { TRANSFER_CATEGORY_NAME } from "@/shared/lib/transfers/constants";
import {
	compareDateOnly,
	getBusinessDateString,
	isDateOnlyPast,
} from "@/shared/utils/date";
import { safeToNumber as toNumber } from "@/shared/utils/number";

const PAYMENT_METHOD_BOLETO = "Boleto";
const TRANSACTION_TYPE_EXPENSE = "Despesa";
const TRANSACTION_TYPE_INCOME = "Receita";
const CONDITION_RECURRING = "Recorrente";
const CONDITION_INSTALLMENT = "Parcelado";

type CurrentPeriodTransactionRow = {
	id: string;
	name: string;
	amount: string | number | null;
	period: string;
	purchaseDate: Date;
	paymentMethod: string;
	condition: string;
	currentInstallment: number | null;
	installmentCount: number | null;
	recurrenceCount: number | null;
	dueDate: Date | null;
	boletoPaymentDate: Date | null;
	isSettled: boolean | null;
	transactionType: string;
	note: string | null;
	isAnticipated: boolean;
	categoryId: string | null;
	categoryName: string | null;
	categoryType: string | null;
	accountId: string | null;
	cardLogo: string | null;
	accountLogo: string | null;
	accountExcludeInitialBalanceFromIncome: boolean | null;
};

type CategoryOption = PurchasesByCategoryData["categories"][number];
type CategoryTransaction =
	PurchasesByCategoryData["transactionsByCategory"][string][number];

type DashboardCurrentPeriodOverview = {
	billsSnapshot: DashboardBillsSnapshot;
	paymentStatusData: PaymentStatusData;
	paymentConditionsData: PaymentConditionsData;
	paymentMethodsData: PaymentMethodsData;
	recurringExpensesData: RecurringExpensesData;
	installmentExpensesData: InstallmentExpensesData;
	topEstablishmentsData: TopEstablishmentsData;
	topExpensesAll: TopExpensesData;
	purchasesByCategoryData: PurchasesByCategoryData;
};

const emptyOverview = (): DashboardCurrentPeriodOverview => ({
	billsSnapshot: {
		bills: [],
		totalPendingAmount: 0,
		pendingCount: 0,
	},
	paymentStatusData: {
		income: { total: 0, confirmed: 0, pending: 0 },
		expenses: { total: 0, confirmed: 0, pending: 0 },
	},
	paymentConditionsData: { conditions: [] },
	paymentMethodsData: { methods: [] },
	recurringExpensesData: { expenses: [] },
	installmentExpensesData: { expenses: [] },
	topEstablishmentsData: { establishments: [] },
	topExpensesAll: { expenses: [] },
	purchasesByCategoryData: {
		categories: [],
		transactionsByCategory: {},
	},
});

const normalizeNote = (note: string | null | undefined) => note?.trim() ?? "";

const isAutoInvoiceNote = (note: string | null | undefined) =>
	normalizeNote(note)
		.toLowerCase()
		.startsWith(ACCOUNT_AUTO_INVOICE_NOTE_PREFIX.toLowerCase());

const isInitialBalanceNote = (note: string | null | undefined) =>
	normalizeNote(note) === INITIAL_BALANCE_NOTE;

const shouldIncludeWithoutAutoInvoice = (note: string | null | undefined) =>
	!isAutoInvoiceNote(note);

const shouldIncludeWithoutAutoGenerated = (note: string | null | undefined) =>
	!isInitialBalanceNote(note) && !isAutoInvoiceNote(note);

const shouldIncludeWithoutRefund = (note: string | null | undefined) =>
	!isRefundNote(note);

const shouldIncludeInPaymentStatus = (row: CurrentPeriodTransactionRow) => {
	if (!shouldIncludeWithoutAutoInvoice(row.note)) {
		return false;
	}

	if (
		isInitialBalanceNote(row.note) &&
		row.accountExcludeInitialBalanceFromIncome === true
	) {
		return false;
	}

	return true;
};

const shouldIncludeNamedItem = (name: string) => {
	const normalized = name.trim().toLowerCase();

	if (normalized === "saldo inicial") {
		return false;
	}

	if (normalized.includes("fatura")) {
		return false;
	}

	return true;
};

const compareDateOnlyAscWithNullsLast = (
	left: string | null,
	right: string | null,
) => {
	if (!left && !right) return 0;
	if (!left) return 1;
	if (!right) return -1;
	return compareDateOnly(left, right);
};

const compareDateOnlyDescWithNullsLast = (
	left: string | null,
	right: string | null,
) => {
	if (!left && !right) return 0;
	if (!left) return 1;
	if (!right) return -1;
	return compareDateOnly(right, left);
};

const buildBillsSnapshot = (
	rows: CurrentPeriodTransactionRow[],
): DashboardBillsSnapshot => {
	const today = getBusinessDateString();
	const bills = rows
		.filter((row) => row.paymentMethod === PAYMENT_METHOD_BOLETO)
		.map((row) => ({
			id: row.id,
			name: row.name,
			amount: Math.abs(toNumber(row.amount)),
			dueDate: row.dueDate ? row.dueDate.toISOString().slice(0, 10) : null,
			boletoPaymentDate: row.boletoPaymentDate
				? row.boletoPaymentDate.toISOString().slice(0, 10)
				: null,
			isSettled: Boolean(row.isSettled),
			accountId: row.accountId ?? null,
			transactionType: row.transactionType,
		}))
		.sort((a, b) => {
			if (a.isSettled !== b.isSettled) {
				return a.isSettled ? 1 : -1;
			}

			if (!a.isSettled && !b.isSettled) {
				const aIsOverdue = a.dueDate ? isDateOnlyPast(a.dueDate, today) : false;
				const bIsOverdue = b.dueDate ? isDateOnlyPast(b.dueDate, today) : false;

				if (aIsOverdue !== bIsOverdue) {
					return aIsOverdue ? -1 : 1;
				}

				const dueDateDiff = compareDateOnlyAscWithNullsLast(
					a.dueDate,
					b.dueDate,
				);
				if (dueDateDiff !== 0) {
					return dueDateDiff;
				}

				const amountDiff = b.amount - a.amount;
				if (amountDiff !== 0) {
					return amountDiff;
				}
			}

			if (a.isSettled && b.isSettled) {
				const paidAtDiff = compareDateOnlyDescWithNullsLast(
					a.boletoPaymentDate,
					b.boletoPaymentDate,
				);
				if (paidAtDiff !== 0) {
					return paidAtDiff;
				}

				const amountDiff = b.amount - a.amount;
				if (amountDiff !== 0) {
					return amountDiff;
				}
			}

			return a.name.localeCompare(b.name, "pt-BR");
		});

	const pendingBills = bills.filter((bill) => !bill.isSettled);

	return {
		bills,
		totalPendingAmount: pendingBills.reduce(
			(total, bill) => total + bill.amount,
			0,
		),
		pendingCount: pendingBills.length,
	};
};

const buildPaymentStatusData = (
	rows: CurrentPeriodTransactionRow[],
): PaymentStatusData => {
	const result: PaymentStatusData = {
		income: { total: 0, confirmed: 0, pending: 0 },
		expenses: { total: 0, confirmed: 0, pending: 0 },
	};

	for (const row of rows) {
		if (
			!shouldIncludeInPaymentStatus(row) ||
			(row.transactionType !== TRANSACTION_TYPE_INCOME &&
				row.transactionType !== TRANSACTION_TYPE_EXPENSE)
		) {
			continue;
		}

		const amount = toNumber(row.amount);
		const isRefund = isRefundNote(row.note);

		if (isRefund) {
			const targetKey = row.isSettled === true ? "confirmed" : "pending";
			result.expenses[targetKey] -= Math.abs(amount);
			continue;
		}

		const isExpense = row.transactionType === TRANSACTION_TYPE_EXPENSE;
		const target = isExpense ? result.expenses : result.income;
		const displayAmount = isExpense ? Math.abs(amount) : amount;

		if (row.isSettled === true) {
			target.confirmed += displayAmount;
		} else {
			target.pending += displayAmount;
		}
	}

	result.expenses.confirmed = Math.max(0, result.expenses.confirmed);
	result.expenses.pending = Math.max(0, result.expenses.pending);
	result.income.total = result.income.confirmed + result.income.pending;
	result.expenses.total = result.expenses.confirmed + result.expenses.pending;

	return result;
};

const buildExpenseBreakdown = (
	rows: CurrentPeriodTransactionRow[],
	field: "condition" | "paymentMethod",
) => {
	const groups = new Map<
		string,
		{
			amount: number;
			transactions: number;
		}
	>();

	for (const row of rows) {
		if (
			row.transactionType !== TRANSACTION_TYPE_EXPENSE ||
			!shouldIncludeWithoutAutoGenerated(row.note)
		) {
			continue;
		}

		const key = row[field];
		const current = groups.get(key) ?? { amount: 0, transactions: 0 };
		current.amount += Math.abs(toNumber(row.amount));
		current.transactions += 1;
		groups.set(key, current);
	}

	const entries = Array.from(groups.entries()).map(([key, value]) => ({
		key,
		amount: value.amount,
		transactions: value.transactions,
	}));
	const overallTotal = entries.reduce(
		(total, entry) => total + entry.amount,
		0,
	);

	return entries
		.map((entry) => ({
			key: entry.key,
			amount: entry.amount,
			transactions: entry.transactions,
			percentage:
				overallTotal > 0
					? Number(((entry.amount / overallTotal) * 100).toFixed(2))
					: 0,
		}))
		.sort((a, b) => b.amount - a.amount);
};

const buildPaymentConditionsData = (
	rows: CurrentPeriodTransactionRow[],
): PaymentConditionsData => ({
	conditions: buildExpenseBreakdown(rows, "condition").map((entry) => ({
		condition: entry.key,
		amount: entry.amount,
		transactions: entry.transactions,
		percentage: entry.percentage,
	})),
});

const buildPaymentMethodsData = (
	rows: CurrentPeriodTransactionRow[],
): PaymentMethodsData => ({
	methods: buildExpenseBreakdown(rows, "paymentMethod").map((entry) => ({
		paymentMethod: entry.key,
		amount: entry.amount,
		transactions: entry.transactions,
		percentage: entry.percentage,
	})),
});

const buildRecurringExpensesData = (
	rows: CurrentPeriodTransactionRow[],
): RecurringExpensesData => ({
	expenses: rows
		.filter(
			(row) =>
				row.transactionType === TRANSACTION_TYPE_EXPENSE &&
				row.condition === CONDITION_RECURRING &&
				shouldIncludeWithoutAutoGenerated(row.note),
		)
		.map((row) => ({
			id: row.id,
			name: row.name,
			amount: Math.abs(toNumber(row.amount)),
			paymentMethod: row.paymentMethod,
			recurrenceCount: row.recurrenceCount,
		})),
});

const buildInstallmentExpensesData = (
	rows: CurrentPeriodTransactionRow[],
): InstallmentExpensesData => ({
	expenses: rows
		.filter(
			(row) =>
				row.transactionType === TRANSACTION_TYPE_EXPENSE &&
				row.condition === CONDITION_INSTALLMENT &&
				row.isAnticipated === false &&
				shouldIncludeWithoutAutoGenerated(row.note),
		)
		.map((row) => ({
			id: row.id,
			name: row.name,
			amount: Math.abs(toNumber(row.amount)),
			paymentMethod: row.paymentMethod,
			currentInstallment: row.currentInstallment,
			installmentCount: row.installmentCount,
			dueDate: row.dueDate,
			purchaseDate: row.purchaseDate,
			period: row.period,
			isSettled: row.isSettled,
		}))
		.sort((a, b) => {
			const remainingA =
				a.installmentCount && a.currentInstallment
					? a.installmentCount - a.currentInstallment
					: 0;
			const remainingB =
				b.installmentCount && b.currentInstallment
					? b.installmentCount - b.currentInstallment
					: 0;

			return remainingA - remainingB;
		}),
});

const mapTopExpense = (row: CurrentPeriodTransactionRow): TopExpense => ({
	id: row.id,
	name: row.name,
	amount: Math.abs(toNumber(row.amount)),
	purchaseDate: row.purchaseDate,
	paymentMethod: row.paymentMethod,
	logo: row.cardLogo ?? row.accountLogo ?? null,
});

const buildTopExpensesData = (
	rows: CurrentPeriodTransactionRow[],
): TopExpensesData => ({
	expenses: rows
		.filter(
			(row) =>
				row.transactionType === TRANSACTION_TYPE_EXPENSE &&
				shouldIncludeWithoutAutoGenerated(row.note),
		)
		.sort((a, b) => toNumber(a.amount) - toNumber(b.amount))
		.slice(0, 10)
		.map(mapTopExpense),
});

const buildTopEstablishmentsData = (
	rows: CurrentPeriodTransactionRow[],
): TopEstablishmentsData => {
	const groups = new Map<
		string,
		{
			amount: number;
			occurrences: number;
			logo: string | null;
		}
	>();

	for (const row of rows) {
		if (
			row.transactionType !== TRANSACTION_TYPE_EXPENSE ||
			!shouldIncludeWithoutAutoGenerated(row.note)
		) {
			continue;
		}

		const current = groups.get(row.name) ?? {
			amount: 0,
			occurrences: 0,
			logo: row.cardLogo ?? row.accountLogo ?? null,
		};

		current.amount += toNumber(row.amount);
		current.occurrences += 1;
		current.logo = current.logo ?? row.cardLogo ?? row.accountLogo ?? null;
		groups.set(row.name, current);
	}

	const topRows = Array.from(groups.entries())
		.map(([name, value]) => ({
			id: name,
			name,
			amount: Math.abs(value.amount),
			occurrences: value.occurrences,
			logo: value.logo,
		}))
		.sort((a, b) => {
			if (a.occurrences !== b.occurrences) {
				return b.occurrences - a.occurrences;
			}

			return b.amount - a.amount;
		})
		.slice(0, 10);

	return {
		establishments: topRows.filter((row) => shouldIncludeNamedItem(row.name)),
	};
};

const buildPurchasesByCategoryData = (
	rows: CurrentPeriodTransactionRow[],
): PurchasesByCategoryData => {
	const categoriesMap = new Map<string, CategoryOption>();
	const transactionsByCategory: Record<string, CategoryTransaction[]> = {};

	for (const row of rows) {
		if (
			!row.categoryId ||
			!row.categoryName ||
			!row.categoryType ||
			!["despesa", "receita"].includes(row.categoryType) ||
			row.categoryName === TRANSFER_CATEGORY_NAME ||
			!shouldIncludeWithoutAutoGenerated(row.note) ||
			!shouldIncludeWithoutRefund(row.note) ||
			!shouldIncludeNamedItem(row.name)
		) {
			continue;
		}

		if (!categoriesMap.has(row.categoryId)) {
			categoriesMap.set(row.categoryId, {
				id: row.categoryId,
				name: row.categoryName,
				type: row.categoryType,
			});
		}

		const transactionList = transactionsByCategory[row.categoryId] ?? [];
		if (transactionList.length < 10) {
			transactionList.push({
				id: row.id,
				name: row.name,
				amount: Math.abs(toNumber(row.amount)),
				purchaseDate: row.purchaseDate,
				logo: row.cardLogo ?? row.accountLogo ?? null,
			});
			transactionsByCategory[row.categoryId] = transactionList;
		}
	}

	return {
		categories: Array.from(categoriesMap.values()).sort((a, b) => {
			if (a.type !== b.type) {
				return a.type === "receita" ? -1 : 1;
			}

			return a.name.localeCompare(b.name, "pt-BR");
		}),
		transactionsByCategory,
	};
};

export async function fetchDashboardCurrentPeriodOverview(
	userId: string,
	period: string,
): Promise<DashboardCurrentPeriodOverview> {
	const adminPayerId = await getAdminPayerId(userId);
	if (!adminPayerId) {
		return emptyOverview();
	}

	const rows = (await db
		.select({
			id: transactions.id,
			name: transactions.name,
			amount: transactions.amount,
			period: transactions.period,
			purchaseDate: transactions.purchaseDate,
			paymentMethod: transactions.paymentMethod,
			condition: transactions.condition,
			currentInstallment: transactions.currentInstallment,
			installmentCount: transactions.installmentCount,
			recurrenceCount: transactions.recurrenceCount,
			dueDate: transactions.dueDate,
			boletoPaymentDate: transactions.boletoPaymentDate,
			isSettled: transactions.isSettled,
			transactionType: transactions.transactionType,
			note: transactions.note,
			isAnticipated: transactions.isAnticipated,
			categoryId: transactions.categoryId,
			categoryName: categories.name,
			categoryType: categories.type,
			accountId: transactions.accountId,
			cardLogo: cards.logo,
			accountLogo: financialAccounts.logo,
			accountExcludeInitialBalanceFromIncome:
				financialAccounts.excludeInitialBalanceFromIncome,
		})
		.from(transactions)
		.leftJoin(cards, eq(transactions.cardId, cards.id))
		.leftJoin(
			financialAccounts,
			eq(transactions.accountId, financialAccounts.id),
		)
		.leftJoin(categories, eq(transactions.categoryId, categories.id))
		.where(
			and(
				eq(transactions.userId, userId),
				eq(transactions.period, period),
				eq(transactions.payerId, adminPayerId),
				excludeTransactionsFromExcludedAccounts(),
			),
		)
		.orderBy(
			desc(transactions.purchaseDate),
			desc(transactions.createdAt),
		)) as CurrentPeriodTransactionRow[];

	return {
		billsSnapshot: buildBillsSnapshot(rows),
		paymentStatusData: buildPaymentStatusData(rows),
		paymentConditionsData: buildPaymentConditionsData(rows),
		paymentMethodsData: buildPaymentMethodsData(rows),
		recurringExpensesData: buildRecurringExpensesData(rows),
		installmentExpensesData: buildInstallmentExpensesData(rows),
		topEstablishmentsData: buildTopEstablishmentsData(rows),
		topExpensesAll: buildTopExpensesData(rows),
		purchasesByCategoryData: buildPurchasesByCategoryData(rows),
	};
}
