import { NextRequest, NextResponse } from "next/server";
import { queryRaw, queryRow } from "@/lib/db";
import { getCurrentUser } from "@/lib/auth";
import { canAccessTransactions } from "@/lib/queries";
import {
  ITEM_VERDICTS,
  RATINGS,
  merchantForTransaction,
  merchantVerdict,
  type ItemOpinion,
  type OrderReview,
  type Rating,
} from "@/lib/order-reviews";

/**
 * Verdicts on one delivery order, plus what was said about this merchant
 * before.
 *
 * Both halves come back together on purpose: the panel is useless without the
 * history — the whole reason to open it is to see whether this place has
 * disappointed us before. Two round trips would let it render the form first
 * and the warning second, which is the order that lets you re-order by
 * mistake.
 *
 * `reviews` is a list, not one row. A shared meal has two opinions and they
 * routinely disagree; collapsing them to one would keep whichever was saved
 * last and silently discard the other person's.
 */

async function authorise(req: NextRequest, id: string) {
  const user = await getCurrentUser(req);
  if (!user) return { error: NextResponse.json({ error: "Unauthorized" }, { status: 403 }) };
  if (!(await canAccessTransactions(user.id, [Number(id)]))) {
    return { error: NextResponse.json({ error: "Forbidden" }, { status: 403 }) };
  }
  return { user };
}

const SELECT_REVIEWS = `
  SELECT r.transaction_id, r.participant_id, p.name AS participant_name,
         r.rating, r.order_again, r.note, r.item_verdicts, r.updated_at
    FROM order_reviews r
    JOIN participants p ON p.id = r.participant_id
   WHERE r.transaction_id = $1
   ORDER BY r.participant_id`;

/**
 * Everything the order panel needs that is not the receipt itself.
 *
 * The splits come back here rather than from a separate endpoint because the
 * panel asks one question — "was this shared, and what did we think of it" —
 * and the sharing half is answered by whether a split exists. A second request
 * would let the verdict render before the share state, which is the order that
 * invites a duplicate split.
 */
async function panelState(transactionId: number) {
  const [reviews, splits, merchant] = await Promise.all([
    queryRaw<OrderReview>(SELECT_REVIEWS, [transactionId]),
    queryRaw<{ participant_id: number; share_percent: string }>(
      `SELECT participant_id, share_percent FROM transaction_splits
        WHERE transaction_id = $1 ORDER BY participant_id`,
      [transactionId]
    ),
    merchantForTransaction(transactionId),
  ]);
  return {
    reviews,
    splits,
    merchant: await merchantVerdict(merchant, transactionId),
  };
}

export async function GET(
  req: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const auth = await authorise(req, id);
  if (auth.error) return auth.error;
  const transactionId = Number(id);

  return NextResponse.json(await panelState(transactionId));
}

/**
 * Record or change one person's verdict.
 *
 * Upsert rather than insert: a verdict is an opinion and opinions get revised.
 * `ON CONFLICT (transaction_id, participant_id)` keeps one row per person per
 * order however many times the buttons are pressed — and, critically, lets the
 * second person's verdict land without touching the first.
 *
 * A null rating is meaningful — it clears the verdict rather than deleting the
 * row, so a note and the item opinions survive changing your mind about the
 * overall call.
 */
export async function PUT(
  req: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const auth = await authorise(req, id);
  if (auth.error) return auth.error;
  const transactionId = Number(id);

  let body: {
    participant_id?: number;
    rating?: Rating | null;
    order_again?: boolean | null;
    note?: string | null;
    item_verdicts?: ItemOpinion[] | null;
  };
  try {
    body = await req.json();
  } catch {
    return NextResponse.json({ error: "invalid JSON" }, { status: 400 });
  }

  // Defaults to whoever is signed in, NOT to the owner: Sonu authenticates
  // through the same Traefik OAuth as participant 4, so an owner default would
  // silently file her verdict under his name. An explicit participant_id is
  // still honoured — one person entering both opinions at the table is the
  // common case in a two-person household.
  const participantId = body.participant_id ?? auth.user!.id;

  const rating = body.rating ?? null;
  if (rating !== null && !RATINGS.includes(rating)) {
    // The DB has the same CHECK constraint; failing here gives a usable message
    // instead of a 500 carrying a Postgres constraint name.
    return NextResponse.json(
      { error: `rating must be one of ${RATINGS.join(", ")} or null` },
      { status: 400 }
    );
  }

  const note = typeof body.note === "string" ? body.note.trim() || null : null;

  // An ABSENT item_verdicts means "leave them alone"; an empty array means
  // "clear them". Without that distinction, saving a note from a form that
  // does not carry the item state silently wipes every per-item opinion — the
  // same shape as the bug that reset `settled` on split rewrites, and just as
  // invisible on screen.
  const keepItems = body.item_verdicts === undefined;

  // Drop anything malformed rather than reject the whole save: the rating and
  // the note are the parts the user is watching, and failing their edit over a
  // bad item entry loses the input they actually gave.
  const itemVerdicts: ItemOpinion[] = (body.item_verdicts ?? [])
    .filter(
      (v): v is ItemOpinion =>
        !!v &&
        typeof v.item === "string" &&
        v.item.trim().length > 0 &&
        ITEM_VERDICTS.includes(v.verdict)
    )
    .map((v) => ({ item: v.item.trim(), verdict: v.verdict }));

  // `order_again` is derived when the caller does not say. "bad" and "never"
  // both answer no — you would not choose either again — but only "never"
  // raises the warning on a future order, so the blacklist stays sharp.
  const orderAgain =
    body.order_again ??
    (rating === null ? null : rating !== "never" && rating !== "bad");

  await queryRow(
    `INSERT INTO order_reviews (transaction_id, participant_id, rating, order_again, note, item_verdicts)
     VALUES ($1, $2, $3, $4, $5, $6::jsonb)
     ON CONFLICT (transaction_id, participant_id) DO UPDATE
       SET rating = EXCLUDED.rating,
           order_again = EXCLUDED.order_again,
           note = EXCLUDED.note,
           item_verdicts = CASE WHEN $7::boolean
                                THEN order_reviews.item_verdicts
                                ELSE EXCLUDED.item_verdicts END,
           updated_at = now()`,
    [
      transactionId,
      participantId,
      rating,
      orderAgain,
      note,
      JSON.stringify(itemVerdicts),
      keepItems,
    ]
  );

  return NextResponse.json(await panelState(transactionId));
}
