import { Prisma } from '@prisma/client';
import prisma from '../lib/prisma';
import { AppError } from '../middleware/errorHandler';
import { STATUS_TRANSITIONS, OrderStatus, calcOrderTotals } from '@nobel/shared';
import { nextReceiptNo } from './sequences.service';

type TxClient = Prisma.TransactionClient;

export async function finalizeOrder(orderId: string, method: string, userId: string) {
  return prisma.$transaction(async (tx: TxClient) => {
    const order = await tx.workOrder.findUniqueOrThrow({
      where: { id: orderId },
      include: { items: true },
    });

    // Billing is frictionless: any active order can be finalized directly
    // (walk-in flow: create → add items → bill). Only terminal states block.
    if (order.status === 'paid') {
      throw new AppError('Order is already finalized', 400);
    }
    if (order.status === 'closed') {
      throw new AppError('Closed orders cannot be finalized', 400);
    }
    if (order.items.length === 0) {
      throw new AppError('Cannot finalize an order with no line items', 400);
    }

    // Decrement stock for part line items, refusing to go negative
    for (const item of order.items) {
      if (item.kind === 'part' && item.inventoryItemId) {
        const qty = Math.floor(item.qty);
        if (qty <= 0) continue;
        const updated = await tx.inventoryItem.updateMany({
          where: { id: item.inventoryItemId, stock: { gte: qty } },
          data: { stock: { decrement: qty } },
        });
        if (updated.count === 0) {
          throw new AppError(`Insufficient stock for "${item.description}"`, 409);
        }
      }
    }

    const receiptNo = await nextReceiptNo(tx);

    await tx.workOrder.update({
      where: { id: orderId },
      data: { status: 'paid' },
    });

    // Snapshot the money at time of payment so later edits can't rewrite history
    const totals = calcOrderTotals({ items: order.items, discount: order.discount, taxRate: order.taxRate });

    const receipt = await tx.receipt.create({
      data: {
        no: receiptNo,
        orderId,
        method: method as 'card' | 'cash' | 'invoice' | 'bank_transfer',
        status: 'paid',
        subtotal: totals.subtotal,
        discount: totals.discount,
        tax: totals.tax,
        total: totals.total,
      },
    });

    await tx.auditLog.create({
      data: {
        userId,
        action: 'finalize_order',
        entityType: 'WorkOrder',
        entityId: orderId,
        details: { receiptNo, method, total: totals.total },
      },
    });

    return receipt;
  });
}

export async function transitionStatus(
  orderId: string,
  newStatus: string,
  userId: string
) {
  return prisma.$transaction(async (tx: TxClient) => {
    const order = await tx.workOrder.findUniqueOrThrow({ where: { id: orderId } });
    const currentStatus = order.status as OrderStatus;
    const validNext = STATUS_TRANSITIONS[currentStatus] || [];

    if (!validNext.includes(newStatus as OrderStatus)) {
      throw new AppError(
        `Cannot transition from ${order.status} to ${newStatus}`,
        400
      );
    }

    // Guard against a concurrent transition: only update if status is unchanged
    const result = await tx.workOrder.updateMany({
      where: { id: orderId, status: order.status },
      data: { status: newStatus as OrderStatus },
    });
    if (result.count === 0) {
      throw new AppError('Order status changed concurrently, retry', 409);
    }

    await tx.auditLog.create({
      data: {
        userId,
        action: 'update_status',
        entityType: 'WorkOrder',
        entityId: orderId,
        details: { from: order.status, to: newStatus },
      },
    });

    return tx.workOrder.findUniqueOrThrow({ where: { id: orderId } });
  });
}
