import { NextRequest, NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { prisma, queryRaw } from "@/lib/db";

export async function POST(req: NextRequest) {
  const user = await getCurrentUser(req);
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 403 });

  const body = await req.json() as {
    matches: { manual_id: number; statement_tx_id: number }[];
  };

  if (!Array.isArray(body.matches) || body.matches.length === 0) {
    return NextResponse.json({ error: "No matches provided" }, { status: 400 });
  }

  // Verify all manual_ids belong to this user
  const manualIds = body.matches.map((m) => m.manual_id);
  const owned = await queryRaw<{ id: number }>(
    `SELECT id FROM transactions WHERE id = ANY($1::int[]) AND statement_id IS NULL AND owner_id = $2`,
    [manualIds, user.id]
  );
  if (owned.length !== manualIds.length) {
    return NextResponse.json({ error: "One or more transactions not found" }, { status: 404 });
  }

  let reconciled = 0;

  for (const { manual_id, statement_tx_id } of body.matches) {
    await prisma.$transaction(async (tx) => {
      // Copy overrides: manual → statement tx
      const override = await tx.transaction_overrides.findUnique({
        where: { transaction_id: manual_id },
      });
      if (override) {
        await tx.transaction_overrides.upsert({
          where: { transaction_id: statement_tx_id },
          update: {
            category_override: override.category_override,
            merchant_normalized: override.merchant_normalized,
            notes: override.notes,
            my_share_percent: override.my_share_percent,
            updated_at: new Date(),
          },
          create: {
            transaction_id: statement_tx_id,
            category_override: override.category_override,
            merchant_normalized: override.merchant_normalized,
            notes: override.notes,
            my_share_percent: override.my_share_percent,
          },
        });
        await tx.transaction_overrides.deleteMany({ where: { transaction_id: manual_id } });
      }

      // Move tags: manual → statement tx
      const tags = await tx.transaction_tags.findMany({ where: { transaction_id: manual_id } });
      if (tags.length) {
        await tx.transaction_tags.createMany({
          data: tags.map((t) => ({ transaction_id: statement_tx_id, tag_id: t.tag_id })),
          skipDuplicates: true,
        });
        await tx.transaction_tags.deleteMany({ where: { transaction_id: manual_id } });
      }

      // Move splits: manual → statement tx
      const splits = await tx.transaction_splits.findMany({ where: { transaction_id: manual_id } });
      if (splits.length) {
        await tx.transaction_splits.createMany({
          data: splits.map((s) => ({
            transaction_id: statement_tx_id,
            participant_id: s.participant_id,
            share_percent: s.share_percent,
          })),
          skipDuplicates: true,
        });
        await tx.transaction_splits.deleteMany({ where: { transaction_id: manual_id } });
      }

      // Move provenance: manual → statement tx.
      //
      // Overrides, tags and splits above were always carried across; expense_metadata was
      // the one child left behind, which did not matter while every metadata row came from
      // an email that had created its own transaction. It matters now: a scanned grocery
      // receipt puts its line items here, and reconciliation hides the manual row from
      // every figure — so without this the shop's contents disappear at exactly the moment
      // the statement line appears, and `COLES 0556 MANOR LAKES` stays as unreadable as it
      // was before the receipt was ever scanned.
      //
      // transaction_id is UNIQUE, so a statement row that already has metadata (an emailed
      // or Paperless copy got there first) keeps it. The pantry row stays attached to the
      // reconciled manual transaction and is flagged, rather than raising a constraint
      // violation or silently overwriting the other source.
      const moved = await tx.$executeRawUnsafe(
        `UPDATE expense_metadata SET transaction_id = $1
          WHERE transaction_id = $2
            AND NOT EXISTS (SELECT 1 FROM expense_metadata other WHERE other.transaction_id = $1)`,
        statement_tx_id,
        manual_id
      );
      if (moved === 0) {
        await tx.$executeRawUnsafe(
          `UPDATE expense_metadata
              SET flags = coalesce(flags, '[]'::jsonb) || '["metadata_collision_on_reconcile"]'::jsonb
            WHERE transaction_id = $1`,
          manual_id
        );
      }

      // Mark manual tx as reconciled (link to statement tx)
      await tx.$executeRawUnsafe(
        `UPDATE transactions SET reconciled_with_id = $1 WHERE id = $2`,
        statement_tx_id,
        manual_id
      );
    });
    reconciled++;
  }

  return NextResponse.json({ reconciled });
}
