import { SupplierPaymentRepository } from "../repositories/SupplierPaymentRepository.js";
import prisma from "../config/prisma.js";
import { Prisma } from "../generated/prisma/client.js";
import whatsappService from "./whatsapp.service.js";

export class SupplierPaymentService {
  constructor(private readonly supplierPaymentRepository = new SupplierPaymentRepository()) { }

  async createPayment(data: any) {
    const supplier = await prisma.supplier.findUnique({
      where: { id: data.supplierId },
    });

    if (!supplier) {
      throw new Error("Supplier not found");
    }

    const paymentAmount = new Prisma.Decimal(data.amount);

    if (paymentAmount.lte(0)) {
      throw new Error("Payment amount must be greater than 0");
    }

    if (paymentAmount.gt(supplier.outstandingBalance)) {
      throw new Error(
        `Payment amount cannot exceed the supplier's outstanding balance (${supplier.outstandingBalance.toFixed(2)}).`
      );
    }

    const createData = {
      supplierId: data.supplierId,
      paymentDate: new Date(data.paymentDate),
      amount: paymentAmount,
      paymentMode: data.paymentMode,
      referenceNo: data.referenceNo,
      remarks: data.remarks,
      createdBy: data.createdBy ?? null,
    };

    const payment = await prisma.$transaction(async (tx) => {
      // 1. Insert into supplier_payments
      const createdPayment = await tx.supplierPayment.create({
        data: createData,
        include: {
          supplier: { select: { name: true } },
        },
      });

      // 2. Decrement supplier outstandingBalance
      await tx.supplier.update({
        where: { id: data.supplierId },
        data: {
          outstandingBalance: {
            decrement: paymentAmount,
          },
        },
      });

      return createdPayment;
    });

    return payment;
  }

  async sendWhatsappPayment(paymentId: bigint): Promise<void> {
    const payment = await prisma.supplierPayment.findUnique({
      where: { id: paymentId },
      include: { supplier: true },
    });

    if (!payment || payment.deletedAt) {
      throw new Error("Supplier payment not found");
    }

    if (payment.isWhatsappMessageSent) {
      throw new Error("WhatsApp message already sent for this payment");
    }

    const phone = payment.supplier?.mobileNo;
    if (!phone) {
      throw new Error("No phone number available to send WhatsApp message");
    }

    if (!whatsappService.isConfigured()) {
      throw new Error("WhatsApp service is not configured");
    }

    const paymentDateObj = new Date(payment.paymentDate);
    const formattedDate = !isNaN(paymentDateObj.getTime())
      ? paymentDateObj.toLocaleDateString("en-IN", {
          day: "2-digit",
          month: "short",
          year: "numeric",
        })
      : String(payment.paymentDate);

    await whatsappService.sendSupplierPaymentMessage({
      phone,
      supplierName: payment.supplier.name,
      paymentAmount: payment.amount.toString(),
      paymentDate: formattedDate,
      paymentMode: String(payment.paymentMode ?? "-"),
      referenceNumber: String(payment.referenceNo || "-"),
      remainingBalance: payment.supplier.outstandingBalance.toString(),
    });

    await prisma.supplierPayment.update({
      where: { id: paymentId },
      data: { isWhatsappMessageSent: true },
    });
  }

  async updatePayment(id: bigint, data: any, userId?: bigint) {
    return await prisma.$transaction(async (tx) => {
      const existingPayment = await tx.supplierPayment.findUnique({
        where: { id },
        include: { supplier: true },
      });

      if (!existingPayment) {
        throw new Error("Supplier payment not found");
      }

      const oldAmount = existingPayment.amount;
      let newAmount = oldAmount;
      if (data.amount !== undefined) {
        newAmount = new Prisma.Decimal(data.amount);
        if (newAmount.lte(0)) {
          throw new Error("Payment amount must be greater than 0");
        }
      }

      const difference = newAmount.sub(oldAmount);

      if (difference.gt(existingPayment.supplier.outstandingBalance)) {
        throw new Error(
          `Updated payment amount cannot exceed the supplier's outstanding balance.`
        );
      }

      const updatedPayment = await tx.supplierPayment.update({
        where: { id },
        data: {
          paymentDate: data.paymentDate ? new Date(data.paymentDate) : existingPayment.paymentDate,
          amount: newAmount,
          paymentMode: data.paymentMode ?? existingPayment.paymentMode,
          referenceNo: data.referenceNo !== undefined ? data.referenceNo : existingPayment.referenceNo,
          remarks: data.remarks !== undefined ? data.remarks : existingPayment.remarks,
        },
      });

      if (!difference.isZero()) {
        await tx.supplier.update({
          where: { id: existingPayment.supplierId },
          data: {
            outstandingBalance: {
              decrement: difference,
            },
          },
        });
      }

      await tx.auditLog.create({
        data: {
          entityName: "SupplierPayment",
          entityId: id,
          action: "UPDATE",
          oldValues: {
            amount: existingPayment.amount.toNumber(),
            paymentDate: existingPayment.paymentDate.toISOString(),
            paymentMode: existingPayment.paymentMode,
            referenceNo: existingPayment.referenceNo,
            remarks: existingPayment.remarks,
          },
          newValues: {
            amount: updatedPayment.amount.toNumber(),
            paymentDate: updatedPayment.paymentDate.toISOString(),
            paymentMode: updatedPayment.paymentMode,
            referenceNo: updatedPayment.referenceNo,
            remarks: updatedPayment.remarks,
          },
          userId: userId ?? null,
        },
      });

      return updatedPayment;
    });
  }

  async deletePayment(id: bigint, userId?: bigint) {
    return await prisma.$transaction(async (tx) => {
      const existingPayment = await tx.supplierPayment.findUnique({
        where: { id },
      });

      if (!existingPayment) {
        throw new Error("Supplier payment not found");
      }

      // Revert the payment amount on the supplier's outstanding balance
      await tx.supplier.update({
        where: { id: existingPayment.supplierId },
        data: {
          outstandingBalance: {
            increment: existingPayment.amount,
          },
        },
      });

      await tx.supplierPayment.update({
        where: { id },
        data: { deletedAt: new Date() },
      });

      await tx.auditLog.create({
        data: {
          entityName: "SupplierPayment",
          entityId: id,
          action: "DELETE",
          oldValues: {
            amount: existingPayment.amount.toNumber(),
            paymentDate: existingPayment.paymentDate.toISOString(),
            paymentMode: existingPayment.paymentMode,
            referenceNo: existingPayment.referenceNo,
            remarks: existingPayment.remarks,
          },
          userId: userId ?? null,
        },
      });

      return existingPayment;
    });
  }

  async getPayments(
    page: number = 1,
    limit: number = 10,
    supplierId?: bigint,
    dateFromStr?: string,
    dateToStr?: string,
    search?: string
  ) {
    const skip = (page - 1) * limit;

    let dateFrom: Date | undefined;
    let dateTo: Date | undefined;

    if (dateFromStr) dateFrom = new Date(dateFromStr);
    if (dateToStr) dateTo = new Date(dateToStr);

    const { items, total } = await this.supplierPaymentRepository.findAll(
      skip,
      limit,
      supplierId,
      dateFrom,
      dateTo,
      search
    );

    return {
      data: items,
      meta: {
        total,
        page,
        limit,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  async getPaymentById(id: bigint) {
    const payment = await this.supplierPaymentRepository.findById(id);
    if (!payment) {
      throw new Error("Supplier payment not found");
    }
    return payment;
  }

  async getPaymentsForExport(
    supplierId?: bigint,
    dateFromStr?: string,
    dateToStr?: string,
    search?: string
  ) {
    let dateFrom: Date | undefined;
    let dateTo: Date | undefined;

    if (dateFromStr) dateFrom = new Date(dateFromStr);
    if (dateToStr) dateTo = new Date(dateToStr);

    return this.supplierPaymentRepository.findAllForExport(
      supplierId,
      dateFrom,
      dateTo,
      search
    );
  }

  async getPaymentMeta() {
    const suppliers = await prisma.supplier.findMany({
      where: { status: true, deletedAt: null },
      select: { id: true, name: true, outstandingBalance: true },
      orderBy: { name: "asc" },
    });
    return { suppliers };
  }
}

