import { Injectable } from "@nestjs/common"
import { failureResponse, successResponse } from "src/common/response/response"
import { code } from "src/common/response/response.code"
import { messageKey } from "src/constants/message-keys"
import { errorMessage, isEmpty, successMessage } from "src/utils/helpers"
import { ExcelService } from "src/utils/xlsx"
import { ClientCompanyRepository } from "../../clients-companies/repositories/clients-companies.repository"
import { InvoiceRepository } from "../../invoices/repositories/invoice.repository"
import { CreatePaymentDto } from "../dto/create-payment.dto"
import { UpdatePaymentDto } from "../dto/update-payment.dto"
import { Payment } from "../entities/payment.entity"
import { PaymentRepository } from "../repositories/payment.repository"
import { INVOICE_STATUS } from "src/constants/invoice.constant"
import { ConfigService } from "@nestjs/config"
import moment from "moment"

@Injectable()
export class PaymentService {
  constructor(
    private readonly paymentRepository: PaymentRepository,
    private readonly invoiceRepository: InvoiceRepository,
    private readonly clientCompanyRepository: ClientCompanyRepository,
    private readonly configService: ConfigService,
    private readonly excelService: ExcelService,
  ) {}

  async create(createPaymentDto: CreatePaymentDto) {
    const invoice = (await this.invoiceRepository.getByParams({
      where: { id: createPaymentDto.invoice_id },
      findOne: true,
    })) as any

    if (isEmpty(invoice)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Invoice" }),
      )
    }

    const clientIdFromInvoice = invoice.client_id

    const currentReceivedAmount = parseFloat(
      invoice.payment_received_amount || "0",
    )
    const newPaymentAmount = parseFloat(createPaymentDto.amount || "0")
    const newReceivedAmount = currentReceivedAmount + newPaymentAmount
    const totalAmount = parseFloat(invoice.total_amount || "0")

    if (newReceivedAmount > totalAmount) {
      return failureResponse(
        code.BAD_REQUEST,
        `Payment failed: The entered amount is greater than the unpaid balance of this invoice`,
      )
    }

    const payment = new Payment()
    Object.assign(payment, createPaymentDto)

    payment.client_id = clientIdFromInvoice

    const savedPayment = await this.paymentRepository.save(payment)

    // Update invoice status
    if (newReceivedAmount >= totalAmount) {
      // Fully paid
      invoice.status = INVOICE_STATUS.PAID
    } else if (newReceivedAmount > 0 && newReceivedAmount < totalAmount) {
      // Partial payment - check if it was overdue
      if (invoice.status === INVOICE_STATUS.OVERDUE) {
        invoice.status = INVOICE_STATUS.PARTIALLY_OVERDUE
      } else {
        invoice.status = INVOICE_STATUS.PARTIALLY_PAID
      }
    } else {
      // No payment received
      invoice.status = INVOICE_STATUS.DUE
    }

    invoice.payment_received_amount = newReceivedAmount
    await this.invoiceRepository.save(invoice)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_add, { ":data": "Payment" }),
      savedPayment,
    )
  }

  async findAll(filters: any) {
    const defaultPagination = {
      take: this.configService.get<number>("APP.pagination.take"),
      skip: this.configService.get<number>("APP.pagination.skip"),
    }

    const { search, limit, skip, payment_method } = filters || {}

    const where: any = {}

    if (payment_method) {
      where.payment_method = payment_method
    }

    let searchObj: any = {}
    if (search) {
      searchObj = {
        id: parseInt(search),
        Invoice_id: parseInt(search),
        created_at: parseInt(search),
        amount: search,
        reference_number: search,
        payment_method: search,
        "entity_client_company.company_name": search,
        "entity_invoice_customer.customer_name": search,
      }
    }

    const params: any = {
      take: Number(limit) || defaultPagination.take,
      skip: Number(skip) || defaultPagination.skip,
      orderBy: { created_at: "DESC" },
      where,
      search: searchObj,
      select: [
        "id",
        "invoice_id",
        "amount",
        "payment_method",
        "reference_number",
        "payment_date",
        "created_at",
      ],
      relations: [
        "invoice:id,invoice_type,start_date,end_date,due_date,total_amount.customer:id,customer_name,is_medical_tourist",
        "client_company:id,company_name",
      ],
    }

    const payments = await this.paymentRepository.getByParams(params)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Payments" }),
      payments,
    )
  }

  async findOne(id: number) {
    const payment = await this.paymentRepository.getByParams({
      where: { id },
      select: [
        "id",
        "invoice_id",
        "amount",
        "payment_method",
        "reference_number",
        "notes",
        "payment_date",
        "created_at",
      ],
      relations: [
        "invoice:id,invoice_type,start_date,end_date,due_date,total_amount.customer:id,customer_name,is_medical_tourist",
        "client_company:id,company_name",
      ],
      findOne: true,
    })

    if (isEmpty(payment)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Payment" }),
      )
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Payment" }),
      payment,
    )
  }

  async update(id: number, updatePaymentDto: UpdatePaymentDto) {
    const existingPayment = (await this.paymentRepository.getByParams({
      where: { id },
      findOne: true,
    })) as any

    if (isEmpty(existingPayment)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Payment" }),
      )
    }

    // Get the original invoice
    const originalInvoice = (await this.invoiceRepository.getByParams({
      where: { id: existingPayment.invoice_id },
      findOne: true,
    })) as any

    if (isEmpty(originalInvoice)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Invoice" }),
      )
    }

    // If invoice_id is being changed, validate the new invoice
    let newInvoice = originalInvoice
    const isInvoiceChanged =
      updatePaymentDto.invoice_id &&
      updatePaymentDto.invoice_id !== existingPayment.invoice_id

    if (isInvoiceChanged) {
      newInvoice = (await this.invoiceRepository.getByParams({
        where: { id: updatePaymentDto.invoice_id },
        findOne: true,
      })) as any

      if (isEmpty(newInvoice)) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, { ":data": "Invoice" }),
        )
      }
    }

    const oldPaymentAmount = parseFloat(existingPayment.amount || "0")
    const newPaymentAmount = parseFloat(
      updatePaymentDto.amount || oldPaymentAmount.toString(),
    )

    // If invoice is being changed, check if new amount exceeds new invoice total
    if (isInvoiceChanged) {
      const newInvoiceReceivedAmount = parseFloat(
        newInvoice.payment_received_amount || "0",
      )
      const newInvoiceTotalAmount = parseFloat(newInvoice.total_amount || "0")

      if (newInvoiceReceivedAmount + newPaymentAmount > newInvoiceTotalAmount) {
        return failureResponse(
          code.BAD_REQUEST,
          `Payment failed: The entered amount is greater than the unpaid balance of the new invoice`,
        )
      }
    } else {
      // Same invoice, check if updated amount exceeds total
      const currentReceivedAmount = parseFloat(
        originalInvoice.payment_received_amount || "0",
      )
      const totalAmount = parseFloat(originalInvoice.total_amount || "0")
      const adjustedAmount =
        currentReceivedAmount - oldPaymentAmount + newPaymentAmount

      if (adjustedAmount > totalAmount) {
        return failureResponse(
          code.BAD_REQUEST,
          `Payment failed: The entered amount is greater than the unpaid balance of this invoice`,
        )
      }
    }

    // Update the payment
    const paymentEntity = new Payment()
    Object.assign(paymentEntity, updatePaymentDto)
    if (isInvoiceChanged) {
      paymentEntity.client_id = newInvoice.client_id
    }

    const updatedPayment = await this.paymentRepository.save(paymentEntity, {
      id,
    })

    // Update original invoice (subtract old amount)
    if (isInvoiceChanged) {
      const originalInvoiceReceivedAmount = parseFloat(
        originalInvoice.payment_received_amount || "0",
      )
      const originalInvoiceTotalAmount = parseFloat(
        originalInvoice.total_amount || "0",
      )
      originalInvoice.payment_received_amount = Math.max(
        0,
        originalInvoiceReceivedAmount - oldPaymentAmount,
      )

      this.updateInvoiceStatus(
        originalInvoice,
        originalInvoice.payment_received_amount,
        originalInvoiceTotalAmount,
      )
      await this.invoiceRepository.save(originalInvoice)

      // Update new invoice (add new amount)
      const newInvoiceReceivedAmount = parseFloat(
        newInvoice.payment_received_amount || "0",
      )
      const newInvoiceTotalAmount = parseFloat(newInvoice.total_amount || "0")
      newInvoice.payment_received_amount =
        newInvoiceReceivedAmount + newPaymentAmount

      this.updateInvoiceStatus(
        newInvoice,
        newInvoice.payment_received_amount,
        newInvoiceTotalAmount,
      )
      await this.invoiceRepository.save(newInvoice)
    } else {
      // Same invoice, just update the amount difference
      const currentReceivedAmount = parseFloat(
        originalInvoice.payment_received_amount || "0",
      )
      const totalAmount = parseFloat(originalInvoice.total_amount || "0")
      const amountDifference = newPaymentAmount - oldPaymentAmount

      originalInvoice.payment_received_amount =
        currentReceivedAmount + amountDifference
      this.updateInvoiceStatus(
        originalInvoice,
        originalInvoice.payment_received_amount,
        totalAmount,
      )
      await this.invoiceRepository.save(originalInvoice)
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, { ":data": "Payment" }),
      updatedPayment,
    )
  }

  async remove(id: number) {
    const payment = (await this.paymentRepository.getByParams({
      where: { id },
      findOne: true,
    })) as any

    if (isEmpty(payment)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Payment" }),
      )
    }

    // Get the associated invoice
    const invoice = (await this.invoiceRepository.getByParams({
      where: { id: payment.invoice_id },
      findOne: true,
    })) as any

    if (isEmpty(invoice)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Invoice" }),
      )
    }

    // Delete the payment
    await this.paymentRepository.remove({ id })

    // Update invoice amount and status
    const deletedPaymentAmount = parseFloat(payment.amount || "0")
    const currentReceivedAmount = parseFloat(
      invoice.payment_received_amount || "0",
    )
    const newReceivedAmount = currentReceivedAmount - deletedPaymentAmount
    const totalAmount = parseFloat(invoice.total_amount || "0")

    invoice.payment_received_amount = Math.max(0, newReceivedAmount) // Ensure non-negative
    this.updateInvoiceStatus(
      invoice,
      invoice.payment_received_amount,
      totalAmount,
    )
    await this.invoiceRepository.save(invoice)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_removed, { ":data": "Payment" }),
    )
  }

  private updateInvoiceStatus(
    invoice: any,
    receivedAmount: number,
    totalAmount: number,
  ) {
    const currentDate = moment().startOf("day")
    const dueDate = invoice.due_date
      ? moment(invoice.due_date).startOf("day")
      : null

    const isOverdue = dueDate && currentDate.isAfter(dueDate)

    if (receivedAmount >= totalAmount) {
      // Fully paid - always PAID regardless of due date
      invoice.status = INVOICE_STATUS.PAID
    } else if (receivedAmount > 0 && receivedAmount < totalAmount) {
      // Partial payment - check if due date has passed
      if (isOverdue) {
        invoice.status = INVOICE_STATUS.PARTIALLY_OVERDUE
      } else {
        invoice.status = INVOICE_STATUS.PARTIALLY_PAID
      }
    } else {
      // No payment received - check if due date has passed
      if (isOverdue) {
        invoice.status = INVOICE_STATUS.OVERDUE
      } else {
        invoice.status = INVOICE_STATUS.UNPAID
      }
    }
  }

  async excelExport(filters: any) {
    const { search, payment_method } = filters || {}

    const where: any = {}

    if (payment_method) {
      where.payment_method = payment_method
    }

    let searchObj: any = {}
    if (search) {
      searchObj = {
        id: parseInt(search),
        invoice_id: parseInt(search),
        amount: search,
        reference_number: search,
        payment_method: search,
        "entity_client_company.company_name": search,
        "entity_invoice_customer.customer_name": search,
      }
    }

    const params: any = {
      orderBy: { created_at: "DESC" },
      where,
      search: searchObj,
      select: [
        "id",
        "invoice_id",
        "amount",
        "payment_method",
        "reference_number",
        "payment_date",
        "created_at",
      ],
      relations: [
        "invoice:id,invoice_type,start_date,end_date,due_date,total_amount.customer:id,customer_name,is_medical_tourist",
        "client_company:id,company_name",
      ],
    }

    const payments: any = await this.paymentRepository.getByParams(params)

    const excelData = this.formatPaymentDataForExcel(payments)

    const buffer = await this.excelService.generateExcelBuffer(excelData)
    return buffer
  }

  // private formatPaymentDataForExcel(data: any[]) {
  //   const header = [
  //     "Payment ID",
  //     "Invoice ID",
  //     "Customer Name",
  //     "Client Name",
  //     "Amount",
  //     "Payment Method",
  //     "Reference Number",
  //     "Payment Date",
  //   ]

  //   const rows = data.map((payment) => [
  //     payment.id,
  //     payment.invoice?.id,
  //     payment.invoice?.customer?.customer_name,
  //     payment.client_company?.company_name,
  //     payment.amount,
  //     payment.payment_method,
  //     payment.reference_number,
  //     payment.payment_date,
  //   ])

  //   return [header, ...rows]
  // }

  private formatPaymentDataForExcel(data: any[]) {
    const header = [
      "Payment ID",
      "Invoice ID",
      "Customer Name",
      "Client Name",
      "Amount",
      "Payment Method",
      "Reference Number",
      "Payment Date",
    ]

    const formatPaymentMethod = (method: string) => {
      if (!method) return ""

      return method
        .split("_")
        .map(
          (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
        )
        .join(" ")
    }

    const formatAmount = (amount: number) => {
      if (amount === null || amount === undefined) return ""
      return `$${Number(amount).toFixed(2)}`
    }

    const rows = data.map((payment) => [
      payment?.id,
      payment?.invoice?.id || "",
      payment?.invoice?.customer?.customer_name || "",
      payment?.client_company?.company_name || "",
      formatAmount(payment?.amount),
      formatPaymentMethod(payment?.payment_method),
      payment?.reference_number || "",
      moment(payment?.payment_date).format("MM/DD/YYYY") || "",
    ])

    return [header, ...rows]
  }
}
