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

interface SnapshotEntry {
  transaction_id: number;
  had_override: boolean;
  prev_category_override: string | null;
  prev_merchant_normalized: string | null;
  prev_tag_ids: number[];
  prev_splits: { participant_id: number; share_percent: number; settled: boolean }[];
}

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

  const { id } = await params;
  const runId = Number(id);

  const rows = await queryRaw<{
    id: number;
    owner_id: number;
    reverted_at: string | null;
    snapshot: unknown;
  }>(
    `SELECT id, owner_id, reverted_at, snapshot FROM rule_apply_runs WHERE id = $1`,
    [runId]
  );

  if (!rows.length) return NextResponse.json({ error: "Run not found" }, { status: 404 });
  const run = rows[0];
  if (run.owner_id !== user.id) return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
  if (run.reverted_at) return NextResponse.json({ error: "Already reverted" }, { status: 409 });

  const snapshot = (typeof run.snapshot === "string"
    ? JSON.parse(run.snapshot)
    : run.snapshot) as SnapshotEntry[];

  for (const entry of snapshot) {
    const txId = entry.transaction_id;

    // Restore overrides
    if (entry.had_override) {
      await queryRaw(
        `INSERT INTO transaction_overrides (transaction_id, category_override, merchant_normalized)
         VALUES ($1, $2, $3)
         ON CONFLICT (transaction_id) DO UPDATE SET
           category_override = $2,
           merchant_normalized = $3,
           updated_at = NOW()`,
        [txId, entry.prev_category_override, entry.prev_merchant_normalized]
      );
    } else {
      // No override existed before — remove any that were created
      await queryRaw(
        `DELETE FROM transaction_overrides WHERE transaction_id = $1
           AND category_override IS NULL AND merchant_normalized IS NULL`,
        [txId]
      );
      // If override row exists but was only partially set by this run, clear those fields
      await queryRaw(
        `UPDATE transaction_overrides SET
           category_override = NULL,
           merchant_normalized = NULL,
           updated_at = NOW()
         WHERE transaction_id = $1`,
        [txId]
      );
    }

    // Restore tags: remove any that weren't there before, don't touch pre-existing ones
    const prevTagIds = entry.prev_tag_ids;
    if (prevTagIds.length > 0) {
      await queryRaw(
        `DELETE FROM transaction_tags WHERE transaction_id = $1 AND tag_id != ALL($2::int[])`,
        [txId, prevTagIds]
      );
    } else {
      // No tags existed before — remove all tags (they were all added by this run)
      // Note: this only removes tags on transactions that matched this run
      await queryRaw(`DELETE FROM transaction_tags WHERE transaction_id = $1`, [txId]);
    }

    // Restore splits
    await queryRaw(`DELETE FROM transaction_splits WHERE transaction_id = $1`, [txId]);
    for (const s of entry.prev_splits) {
      await queryRaw(
        `INSERT INTO transaction_splits (transaction_id, participant_id, share_percent, settled)
         VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING`,
        [txId, s.participant_id, s.share_percent, s.settled]
      );
    }
    // A snapshot taken before splits were required to total 100% holds the old
    // partial shape, and restoring it verbatim would reintroduce exactly what
    // this run is being undone from. The owner's share is balance-neutral, so
    // completing it cannot change what the revert owes anyone.
    await completeSplit(txId);
  }

  await queryRaw(
    `UPDATE rule_apply_runs SET reverted_at = NOW() WHERE id = $1`,
    [runId]
  );

  return NextResponse.json({ reverted: snapshot.length });
}
