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

export class CustomerReceiptService {
  constructor(
    private readonly customerReceiptRepository = new CustomerReceiptRepository()
  ) {}

  async createReceipt(data: any) {
    const customer = await prisma.customer.findUnique({
      where: { id: data.customerId },
    });
    if (!customer) throw new Error("Customer not found");

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

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

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

    const createData = {
      customerId: data.customerId,
      receiptDate: new Date(data.receiptDate),
      amount: receiptAmount,
      paymentMode: data.paymentMode,
      referenceNo: data.referenceNo,
      remarks: data.remarks,
      createdBy: data.createdBy ?? null,
    };

    const receipt = await prisma.$transaction(async (tx) => {
      // 1. Create Receipt
      const createdReceipt = await tx.customerReceipt.create({
        data: createData,
        include: {
          customer: { select: { name: true } },
        },
      });

      // 2. Decrement Customer Outstanding
      await tx.customer.update({
        where: { id: data.customerId },
        data: { outstandingBalance: { decrement: receiptAmount } },
      });

      return createdReceipt;
    });

    return receipt;
  }

  async sendWhatsappReceipt(receiptId: bigint): Promise<void> {
    const receipt = await prisma.customerReceipt.findUnique({
      where: { id: receiptId },
      include: { customer: true },
    });

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

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

    const phone = receipt.customer?.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 receiptDateObj = new Date(receipt.receiptDate);
    const formattedDate = !isNaN(receiptDateObj.getTime())
      ? receiptDateObj.toLocaleDateString("en-IN", {
          day: "2-digit",
          month: "short",
          year: "numeric",
        })
      : String(receipt.receiptDate);

    await whatsappService.sendCustomerPaymentReceived({
      phone,
      customerName: receipt.customer.name,
      paymentAmount: receipt.amount.toString(),
      paymentDate: formattedDate,
      remainingBalance: receipt.customer.outstandingBalance.toString(),
    });

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

  async updateReceipt(id: bigint, data: any, userId?: bigint) {
    return await prisma.$transaction(async (tx) => {
      const existingReceipt = await tx.customerReceipt.findUnique({
        where: { id },
        include: { customer: true }
      });

      if (!existingReceipt) {
        throw new Error("Customer receipt not found");
      }

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

      const difference = newAmount.sub(oldAmount);

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

      const updatedReceipt = await tx.customerReceipt.update({
        where: { id },
        data: {
          receiptDate: data.receiptDate ? new Date(data.receiptDate) : existingReceipt.receiptDate,
          amount: newAmount,
          paymentMode: data.paymentMode ?? existingReceipt.paymentMode,
          referenceNo: data.referenceNo !== undefined ? data.referenceNo : existingReceipt.referenceNo,
          remarks: data.remarks !== undefined ? data.remarks : existingReceipt.remarks,
        }
      });

      if (!difference.isZero()) {
        await tx.customer.update({
          where: { id: existingReceipt.customerId },
          data: {
            outstandingBalance: {
              decrement: difference
            }
          }
        });
      }

      await tx.auditLog.create({
        data: {
          entityName: "CustomerReceipt",
          entityId: id,
          action: "UPDATE",
          oldValues: {
            amount: existingReceipt.amount.toNumber(),
            receiptDate: existingReceipt.receiptDate.toISOString(),
            paymentMode: existingReceipt.paymentMode,
            referenceNo: existingReceipt.referenceNo,
            remarks: existingReceipt.remarks
          },
          newValues: {
            amount: updatedReceipt.amount.toNumber(),
            receiptDate: updatedReceipt.receiptDate.toISOString(),
            paymentMode: updatedReceipt.paymentMode,
            referenceNo: updatedReceipt.referenceNo,
            remarks: updatedReceipt.remarks
          },
          userId: userId ?? null
        }
      });

      return updatedReceipt;
    });
  }

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

      if (!existingReceipt) {
        throw new Error("Customer receipt not found");
      }

      // Revert the receipt amount on the customer's outstanding balance
      await tx.customer.update({
        where: { id: existingReceipt.customerId },
        data: {
          outstandingBalance: {
            increment: existingReceipt.amount
          }
        }
      });

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

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

      return existingReceipt;
    });
  }

  async getReceipts(
    page: number = 1,
    limit: number = 10,
    customerId?: 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.customerReceiptRepository.findAll(
      skip,
      limit,
      customerId,
      dateFrom,
      dateTo,
      search
    );

    const data = items.map((item: any) => {
      const { createdByUser, ...rest } = item;
      return {
        ...rest,
        createdByName: createdByUser?.name ?? null,
      };
    });

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

  async getReceiptsForExport(
    customerId?: 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.customerReceiptRepository.findAllForExport(
      customerId,
      dateFrom,
      dateTo,
      search
    );
  }

  async getReceiptById(id: bigint) {
    const receipt = await this.customerReceiptRepository.findById(id);
    if (!receipt) throw new Error("Receipt not found");

    const { createdByUser, ...rest } = receipt as any;
    return {
      ...rest,
      createdByName: createdByUser?.name ?? null,
    };
  }

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