import { prisma } from '@/lib/prisma';
import { sanitizeText } from '@/lib/sanitize';
import type { CreateInvoiceInput, AddPaymentInput } from '@/schemas/invoice.schema';

const TAX_RATE = parseFloat(process.env.TAX_RATE || '0.19');

function generateInvoiceNumber(): string {
  const now = new Date();
  const prefix = `FAC-${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`;
  const suffix = Math.random().toString(36).substring(2, 8).toUpperCase();
  return `${prefix}-${suffix}`;
}

export async function listInvoices(filters: {
  status?: string;
  page?: number;
  limit?: number;
  from?: string;
  to?: string;
}) {
  const page = filters.page || 1;
  const limit = filters.limit || 20;
  const skip = (page - 1) * limit;

  const where = {
    ...(filters.status ? { status: filters.status as never } : {}),
    ...(filters.from || filters.to
      ? {
          createdAt: {
            ...(filters.from ? { gte: new Date(filters.from) } : {}),
            ...(filters.to ? { lte: new Date(filters.to) } : {}),
          },
        }
      : {}),
  };

  const [invoices, total] = await prisma.$transaction([
    prisma.invoice.findMany({
      where,
      skip,
      take: limit,
      include: {
        order: {
          select: {
            orderNumber: true,
            table: { select: { number: true } },
            waiter: { select: { name: true } },
          },
        },
        payments: true,
      },
      orderBy: { createdAt: 'desc' },
    }),
    prisma.invoice.count({ where }),
  ]);

  return { invoices, total, page, limit };
}

export async function getInvoiceById(id: string) {
  return prisma.invoice.findUnique({
    where: { id },
    include: {
      order: {
        include: {
          items: {
            include: { product: { select: { id: true, name: true } } },
          },
          table: { select: { number: true, zone: { select: { name: true } } } },
          waiter: { select: { name: true } },
        },
      },
      payments: { include: { cashShift: { select: { id: true, openedAt: true } } } },
    },
  });
}

export async function createInvoice(data: CreateInvoiceInput, cashierId: string) {
  return prisma.$transaction(async (tx) => {
    const order = await tx.order.findUnique({
      where: { id: data.orderId },
      include: {
        items: { where: { status: { not: 'CANCELLED' } } },
      },
    });

    if (!order) throw new Error('ORDER_NOT_FOUND');
    if (order.status === 'BILLED') throw new Error('INVALID_STATUS: Esta orden ya fue facturada');
    if (order.status === 'CANCELLED') throw new Error('INVALID_STATUS: No se puede facturar una orden cancelada');

    // Calcular montos
    const subtotal = order.items.reduce((acc, item) => acc + Number(item.subtotal), 0);
    const discount = data.discountAmount || 0;
    if (discount > subtotal) throw new Error('DISCOUNT_EXCEEDS_SUBTOTAL');
    const tip = data.tipAmount || 0;
    const taxableAmount = subtotal - discount;
    const taxAmount = taxableAmount * TAX_RATE;
    const total = taxableAmount + taxAmount + tip;

    const invoice = await tx.invoice.create({
      data: {
        invoiceNumber: generateInvoiceNumber(),
        orderId: data.orderId,
        status: 'ISSUED',
        subtotal,
        taxAmount,
        tipAmount: tip,
        discountAmount: discount,
        total,
        cashierId,
        notes: data.notes ? sanitizeText(data.notes) : undefined,
      },
      include: { order: { select: { orderNumber: true } }, payments: true },
    });

    // Cambiar estado de la orden
    await tx.order.update({ where: { id: data.orderId }, data: { status: 'BILLED' } });

    return invoice;
  });
}

export async function addPayments(invoiceId: string, data: AddPaymentInput) {
  return prisma.$transaction(async (tx) => {
    const invoice = await tx.invoice.findUnique({
      where: { id: invoiceId },
      include: { payments: true },
    });

    if (!invoice) throw new Error('INVOICE_NOT_FOUND');
    if (invoice.status === 'ANNULLED') throw new Error('INVALID_STATUS: Factura anulada');
    if (invoice.status === 'PAID') throw new Error('INVALID_STATUS: La factura ya está pagada');

    // Verificar turno de caja
    const shiftId = data.payments[0].cashShiftId;
    const shift = await tx.cashShift.findUnique({ where: { id: shiftId } });
    if (!shift || shift.status !== 'OPEN') throw new Error('SHIFT_NOT_OPEN');

    // Crear pagos
    await tx.payment.createMany({
      data: data.payments.map((p) => ({
        invoiceId,
        method: p.method,
        amount: p.amount,
        referenceCode: p.referenceCode ? sanitizeText(p.referenceCode) : undefined,
        cashShiftId: p.cashShiftId,
      })),
    });

    // Verificar si la suma de pagos cubre el total
    const allPayments = await tx.payment.findMany({ where: { invoiceId }, select: { amount: true } });
    const paidTotal = allPayments.reduce((acc, p) => acc + Number(p.amount), 0);

    if (paidTotal >= Number(invoice.total)) {
      await tx.invoice.update({ where: { id: invoiceId }, data: { status: 'PAID' } });
      // Liberar mesa
      const order = await tx.order.findUnique({
        where: { id: invoice.orderId },
        select: { tableId: true },
      });
      if (order?.tableId) {
        await tx.table.update({ where: { id: order.tableId }, data: { status: 'AVAILABLE' } });
      }
    }

    return tx.invoice.findUnique({
      where: { id: invoiceId },
      include: { payments: true },
    });
  });
}

export async function annulInvoice(invoiceId: string, reason: string, userId: string) {
  return prisma.$transaction(async (tx) => {
    const invoice = await tx.invoice.findUnique({
      where: { id: invoiceId },
      select: { id: true, status: true, orderId: true, payments: { select: { id: true } } },
    });
    if (!invoice) throw new Error('INVOICE_NOT_FOUND');
    if (invoice.status !== 'ISSUED') throw new Error('INVALID_STATUS: Solo facturas emitidas pueden anularse');
    if (invoice.payments.length > 0) throw new Error('INVOICE_HAS_PAYMENTS');

    await tx.invoice.update({ where: { id: invoiceId }, data: { status: 'ANNULLED' } });
    await tx.order.update({ where: { id: invoice.orderId }, data: { status: 'SERVED' } });

    return { id: invoiceId, status: 'ANNULLED', reason: sanitizeText(reason) };
  });
}
