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

const VALID_TYPES = ["debit", "credit", "payment", "refund", "fee", "interest", "transfer"];

export async function GET(
  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;
  if (!(await canAccessTransactions(user.id, [Number(id)]))) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }
  const txn = await getTransactionById(Number(id));
  if (!txn) return NextResponse.json({ error: "Not found" }, { status: 404 });
  return NextResponse.json(txn);
}

export async function PATCH(
  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 transactionId = Number(id);
  if (!(await canAccessTransactions(user.id, [transactionId]))) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }
  const body = await req.json();

  const { category, merchant_normalized, notes, transaction_type, my_share_percent, description, amount, transaction_date, trip_id, payment_method, owner_id, release } = body as {
    category?: string;
    merchant_normalized?: string;
    notes?: string;
    transaction_type?: string;
    payment_method?: string | null;
    my_share_percent?: number | null;
    description?: string;
    amount?: number;
    transaction_date?: string;
    trip_id?: number | null;
    owner_id?: number;
    /** Acknowledge that reassigning will remove the row from my own view. */
    release?: boolean;
  };

  if (my_share_percent !== undefined && my_share_percent !== null) {
    if (typeof my_share_percent !== "number" || my_share_percent <= 0 || my_share_percent > 100) {
      return NextResponse.json({ error: "my_share_percent must be between 1 and 100" }, { status: 400 });
    }
  }

  // Direct field edits — only allowed for manual transactions (statement_id IS NULL)
  const directFields = [description, amount, transaction_date].filter((v) => v !== undefined);
  if (directFields.length > 0) {
    const txRows = await queryRaw<{ statement_id: number | null }>(
      `SELECT statement_id FROM transactions WHERE id = $1`,
      [transactionId]
    );
    if (!txRows[0]?.statement_id) {
      const setClauses: string[] = [];
      const params: unknown[] = [];
      let idx = 1;
      if (description !== undefined) { setClauses.push(`description = $${idx++}`); params.push(description); }
      if (amount !== undefined) { setClauses.push(`amount = $${idx++}`); params.push(amount); }
      if (transaction_date !== undefined) { setClauses.push(`transaction_date = $${idx++}`); params.push(transaction_date); }
      if (setClauses.length) {
        params.push(transactionId);
        await queryRaw(`UPDATE transactions SET ${setClauses.join(", ")} WHERE id = $${idx}`, params);
      }
    }
  }

  // transaction_type is a direct correction on the transactions table
  if (transaction_type !== undefined) {
    if (!VALID_TYPES.includes(transaction_type)) {
      return NextResponse.json({ error: "Invalid transaction_type" }, { status: 400 });
    }
    await queryRaw(
      `UPDATE transactions SET transaction_type = $1 WHERE id = $2`,
      [transaction_type, transactionId]
    );
  }

  // payment_method is a property of the transaction itself, not a user override
  // of extracted data, so it lives on the transactions table.
  if (payment_method !== undefined) {
    const VALID_METHODS = ["card", "cash", "bank_transfer", "other"];
    if (payment_method !== null && !VALID_METHODS.includes(payment_method)) {
      return NextResponse.json({ error: "Invalid payment_method" }, { status: 400 });
    }
    await queryRaw(
      `UPDATE transactions SET payment_method = $1 WHERE id = $2`,
      [payment_method, transactionId]
    );
  }

  // Owner — who actually paid. A direct column, not an override: it decides
  // whose account the money left, which every balance and every spend analytic
  // scopes on (MY_SPEND_SCOPE / OWNER_SCOPE).
  //
  // Handled here rather than in the direct-fields block above because it has
  // its own rules, and because getting it wrong is not a cosmetic error: it
  // moves money between two people's ledgers.
  if (owner_id !== undefined) {
    if (typeof owner_id !== "number" || !Number.isInteger(owner_id)) {
      return NextResponse.json({ error: "owner_id must be a participant id" }, { status: 400 });
    }

    const txRows = await queryRaw<{ statement_id: number | null; owner_id: number | null }>(
      `SELECT statement_id, owner_id FROM transactions WHERE id = $1`,
      [transactionId]
    );
    const tx = txRows[0];
    if (!tx) return NextResponse.json({ error: "Not found" }, { status: 404 });

    // A statement row's owner is the statement's owner — the effective owner is
    // COALESCE(t.owner_id, s.owner_id), so setting it here would either be a
    // no-op or would silently detach one row from the account it was extracted
    // from. Reassigning the statement is the correct move and moves its rows
    // with it.
    if (tx.statement_id) {
      return NextResponse.json(
        {
          error:
            "This row came from a statement, so its owner is the statement's owner. Change the statement's owner instead — that moves every row on it.",
          code: "statement_owned",
        },
        { status: 400 }
      );
    }

    const known = await queryRaw(`SELECT id FROM participants WHERE id = $1`, [owner_id]);
    if (!known.length) {
      return NextResponse.json({ error: "Unknown participant" }, { status: 400 });
    }

    // The one-way door. Access is "owner OR holds a split"
    // (canAccessTransactions), so handing a row to someone else while holding
    // no split on it removes it from the caller's list and 404s every route
    // that could put it back — only the new owner can undo it. Splitting first
    // keeps the row reachable AND is what makes the balance correct, so the
    // refusal points at the step that was skipped rather than just blocking.
    if (owner_id !== user.id && !release) {
      const mine = await queryRaw(
        `SELECT 1 FROM transaction_splits WHERE transaction_id = $1 AND participant_id = $2`,
        [transactionId, user.id]
      );
      if (!mine.length) {
        return NextResponse.json(
          {
            error:
              "You hold no split on this transaction, so reassigning it would remove it from your view for good — only the new owner could change it back. Add your split first, or confirm you are giving it away entirely.",
            code: "would_lose_access",
          },
          { status: 409 }
        );
      }
    }

    // Existing splits are deliberately left alone. They record shares, not
    // direction: getParticipantBalances derives who owes whom from ownership,
    // so a 50/50 row flips from "they owe me" to "I owe them" with no rewrite.
    await queryRaw(`UPDATE transactions SET owner_id = $1 WHERE id = $2`, [owner_id, transactionId]);
  }

  // category/merchant/notes/my_share_percent/trip_id go through the overrides table
  const hasOverride = category !== undefined || merchant_normalized !== undefined || notes !== undefined || my_share_percent !== undefined || trip_id !== undefined;
  if (!hasOverride) {
    return NextResponse.json({ ok: true });
  }

  const data: Record<string, unknown> = { updated_at: new Date() };
  if (category !== undefined) data.category_override = category;
  if (merchant_normalized !== undefined) data.merchant_normalized = merchant_normalized;
  if (notes !== undefined) data.notes = notes;
  if (my_share_percent !== undefined) data.my_share_percent = my_share_percent;
  if (trip_id !== undefined) data.trip_id = trip_id;

  const override = await prisma.transaction_overrides.upsert({
    where: { transaction_id: transactionId },
    update: data,
    create: {
      transaction_id: transactionId,
      category_override: category || null,
      merchant_normalized: merchant_normalized || null,
      notes: notes || null,
      my_share_percent: my_share_percent != null ? String(my_share_percent) : null,
      trip_id: trip_id ?? null,
    },
  });

  return NextResponse.json(override);
}
