import { NextRequest, NextResponse } from "next/server"; import { getCurrentUser } from "@/lib/auth"; import { queryRaw } from "@/lib/db"; // Split-adjusted amount helper (positive for spend, negative for refunds) const MY_AMOUNT = `CASE WHEN ts.share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * ts.share_percent / 100 WHEN o.my_share_percent IS NOT NULL THEN COALESCE(t.amount_aud, t.amount) * o.my_share_percent / 100 ELSE COALESCE(t.amount_aud, t.amount) END`; const SPEND_EXPR = ` CASE WHEN t.transaction_type IN ('refund', 'credit') THEN -(${MY_AMOUNT}) ELSE (${MY_AMOUNT}) END `; export async function GET(req: NextRequest) { const user = await getCurrentUser(req); if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const { searchParams } = new URL(req.url); const months = Math.min(24, Math.max(1, Number(searchParams.get("months") || "12"))); const cutoff = new Date(); cutoff.setMonth(cutoff.getMonth() - months); const fromDate = cutoff.toISOString().slice(0, 10); // Merchant aggregates — net spend (debits + fees - refunds/credits) const rows = await queryRaw<{ merchant: string; category: string; debit_count: number; refund_count: number; gross_spend: number; total_refunds: number; net_spend: number; avg_debit: number; first_seen: string; last_seen: string; months_active: number; }>(` SELECT COALESCE(o.merchant_normalized, t.merchant_normalized, t.merchant_name, t.description) as merchant, MODE() WITHIN GROUP (ORDER BY COALESCE(o.category_override, t.category)) as category, COUNT(*) FILTER (WHERE t.transaction_type IN ('debit', 'fee', 'interest'))::int as debit_count, COUNT(*) FILTER (WHERE t.transaction_type IN ('refund', 'credit'))::int as refund_count, COALESCE(SUM( CASE WHEN t.transaction_type IN ('debit', 'fee', 'interest') THEN ${MY_AMOUNT} ELSE 0 END ), 0)::numeric(12,2) as gross_spend, COALESCE(SUM( CASE WHEN t.transaction_type IN ('refund', 'credit') THEN ${MY_AMOUNT} ELSE 0 END ), 0)::numeric(12,2) as total_refunds, SUM(${SPEND_EXPR})::numeric(12,2) as net_spend, AVG( CASE WHEN t.transaction_type IN ('debit', 'fee', 'interest') THEN ${MY_AMOUNT} END )::numeric(10,2) as avg_debit, MIN(t.transaction_date)::text as first_seen, MAX(t.transaction_date)::text as last_seen, COUNT(DISTINCT TO_CHAR(DATE_TRUNC('month', t.transaction_date::date), 'YYYY-MM'))::int as months_active FROM transactions t JOIN statements s ON s.id = t.statement_id LEFT JOIN transaction_overrides o ON o.transaction_id = t.id LEFT JOIN transaction_splits ts ON ts.transaction_id = t.id AND ts.participant_id = $1 WHERE s.owner_id = $1 AND t.transaction_type IN ('debit', 'fee', 'interest', 'refund', 'credit') AND t.transaction_date >= $2 AND COALESCE(o.category_override, t.category) NOT IN ('transfers', 'investment') GROUP BY 1 HAVING SUM(${SPEND_EXPR}) > 0 ORDER BY net_spend DESC LIMIT 200 `, [user.id, fromDate]); // Monthly net trend per merchant (top 50 by net spend) const topMerchants = rows.slice(0, 50).map((r) => r.merchant); interface TrendRow { merchant: string; month: string; total: number } let trendRows: TrendRow[] = []; if (topMerchants.length > 0) { trendRows = await queryRaw(` SELECT COALESCE(o.merchant_normalized, t.merchant_normalized, t.merchant_name, t.description) as merchant, TO_CHAR(DATE_TRUNC('month', t.transaction_date::date), 'YYYY-MM') as month, SUM(${SPEND_EXPR})::numeric(10,2) as total FROM transactions t JOIN statements s ON s.id = t.statement_id LEFT JOIN transaction_overrides o ON o.transaction_id = t.id LEFT JOIN transaction_splits ts ON ts.transaction_id = t.id AND ts.participant_id = $1 WHERE s.owner_id = $1 AND t.transaction_type IN ('debit', 'fee', 'interest', 'refund', 'credit') AND t.transaction_date >= $2 AND COALESCE(o.merchant_normalized, t.merchant_normalized, t.merchant_name, t.description) = ANY($3) AND COALESCE(o.category_override, t.category) NOT IN ('transfers', 'investment') GROUP BY 1, 2 ORDER BY 1, 2 `, [user.id, fromDate, topMerchants]); } const trendByMerchant: Record> = {}; for (const tr of trendRows) { if (!trendByMerchant[tr.merchant]) trendByMerchant[tr.merchant] = {}; trendByMerchant[tr.merchant][tr.month] = Number(tr.total); } const merchants = rows.map((r) => ({ ...r, debit_count: Number(r.debit_count), refund_count: Number(r.refund_count), gross_spend: Number(r.gross_spend), total_refunds: Number(r.total_refunds), net_spend: Number(r.net_spend), avg_debit: Number(r.avg_debit), months_active: Number(r.months_active), monthly_trend: trendByMerchant[r.merchant] || {}, })); return NextResponse.json({ merchants, months }); }