import { NextRequest, NextResponse } from "next/server";
import { prisma, queryRaw } from "@/lib/db";
import { assignTransactionsToTrip, canAccessTransactions } from "@/lib/queries";
import { getCurrentUser } from "@/lib/auth";
import { applyRuleActions, captureSnapshot } from "@/lib/rule-actions";
import type { Actions } from "@/lib/rules";

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();
  const { action, ids, category, merchant_normalized, splits, tag_id } = body as {
    action: string;
    ids: number[];
    category?: string;
    merchant_normalized?: string;
    splits?: { participant_id: number; share_percent: number }[];
    tag_id?: number;
  };

  if (!ids || !Array.isArray(ids) || ids.length === 0) {
    return NextResponse.json({ error: "ids required" }, { status: 400 });
  }

  if (!(await canAccessTransactions(user.id, ids.map(Number)))) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  if (action === "categorize" && category) {
    const ops = ids.map((id) =>
      prisma.transaction_overrides.upsert({
        where: { transaction_id: id },
        update: { category_override: category, updated_at: new Date() },
        create: { transaction_id: id, category_override: category },
      })
    );
    await prisma.$transaction(ops);
    return NextResponse.json({ updated: ids.length });
  }

  if (action === "normalize" && merchant_normalized) {
    const ops = ids.map((id) =>
      prisma.transaction_overrides.upsert({
        where: { transaction_id: id },
        update: { merchant_normalized, updated_at: new Date() },
        create: { transaction_id: id, merchant_normalized },
      })
    );
    await prisma.$transaction(ops);
    return NextResponse.json({ updated: ids.length });
  }

  if (action === "split" && Array.isArray(splits) && splits.length > 0) {
    const total = splits.reduce((s, x) => s + x.share_percent, 0);
    if (Math.abs(total - 100) > 0.01) {
      return NextResponse.json({ error: "Shares must sum to 100%" }, { status: 400 });
    }
    await prisma.$transaction(
      ids.flatMap((id) => [
        prisma.transaction_splits.deleteMany({ where: { transaction_id: id } }),
        prisma.transaction_splits.createMany({
          data: splits.map((s) => ({
            transaction_id: id,
            participant_id: s.participant_id,
            share_percent: s.share_percent,
          })),
        }),
      ])
    );
    return NextResponse.json({ updated: ids.length });
  }

  if ((action === "tag" || action === "untag") && tag_id) {
    if (action === "tag") {
      for (const id of ids) {
        await queryRaw(
          `INSERT INTO transaction_tags (transaction_id, tag_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
          [id, tag_id]
        );
      }
    } else {
      await queryRaw(
        `DELETE FROM transaction_tags WHERE transaction_id = ANY($1::int[]) AND tag_id = $2`,
        [ids, tag_id]
      );
    }
    return NextResponse.json({ updated: ids.length });
  }

  // Quick action: fire a saved rule's actions at the current selection. The
  // selection replaces the rule's conditions, which are not evaluated at all.
  // Recorded as a rule_apply_run so it can be reverted like any other run.
  if (action === "apply_rule") {
    const { rule_id } = body as { rule_id?: number };
    if (!rule_id) return NextResponse.json({ error: "rule_id required" }, { status: 400 });

    const rules = await queryRaw<{ id: number; name: string; actions: unknown }>(
      `SELECT id, name, actions FROM rules WHERE id = $1 AND owner_id = $2`,
      [Number(rule_id), user.id]
    );
    if (!rules.length) return NextResponse.json({ error: "Rule not found" }, { status: 404 });

    const actions = (typeof rules[0].actions === "string"
      ? JSON.parse(rules[0].actions)
      : rules[0].actions) as Actions;

    const snapshot = await captureSnapshot(ids.map(Number));
    for (const id of ids) {
      await applyRuleActions(Number(id), actions);
    }

    const run = await queryRaw<{ id: number }>(
      `INSERT INTO rule_apply_runs (owner_id, split_from, matched, transactions_affected, snapshot,
         rule_id, rule_name, source)
       VALUES ($1, NULL, $2, $3, $4, $5, $6, 'selection') RETURNING id`,
      [user.id, ids.length, ids.length, JSON.stringify(snapshot),
       rules[0].id, rules[0].name]
    );

    return NextResponse.json({ updated: ids.length, run_id: run[0].id, rule: rules[0].name });
  }

  if (action === "assign_trip") {
    const { trip_id } = body as { ids: number[]; trip_id: number | null };
    try {
      // `updated` is what actually moved, not what was asked for — ids the
      // caller cannot see are skipped rather than silently applied.
      const updated = await assignTransactionsToTrip(trip_id, ids, user.id);
      return NextResponse.json({ updated, requested: ids.length });
    } catch (e) {
      return NextResponse.json(
        { error: e instanceof Error ? e.message : "Failed to assign" },
        { status: 403 }
      );
    }
  }

  return NextResponse.json({ error: "Invalid action" }, { status: 400 });
}
