import { Injectable } from "@nestjs/common"
import { CreateInvoiceDto } from "../dto/create-invoice.dto"
import { UpdateInvoiceDto } from "../dto/update-invoice.dto"
import { InvoiceRepository } from "../repositories/invoice.repository"
import { errorMessage, isEmpty, successMessage } from "src/utils/helpers"
import { ClientCompanyRepository } from "../../clients-companies/repositories/clients-companies.repository"
import { CustomerRepository } from "../../customers/repositories/customers.repository"
import { Invoice } from "../entities/invoice.entity"
import { failureResponse, successResponse } from "src/common/response/response"
import { code } from "src/common/response/response.code"
import { messageKey } from "src/constants/message-keys"
import { FilterInvoiceDto } from "../dto/filter-invoice.dto"
import { ConfigService } from "@nestjs/config"
import { ExcelService } from "src/utils/xlsx"
import { InjectRepository } from "@nestjs/typeorm"
import { Trip } from "../../trips/entities/trip.entity"
import { Repository } from "typeorm"
import { TripBasePricingRepository } from "../../trips/repositories/trip-base-price.repository"
import { InvoiceTripRepository } from "../repositories/trip-invoice.repository"
import { TripAddOnsPricingRepository } from "../../trips/repositories/trip-addons-pricing.repository"
import { InvoiceSettingsRepository } from "../../clients-companies/repositories/invoice-settings.repository"
import { CityRepository } from "../../city/repositories/city.repository"
import { STATUS } from "src/constants/trip.constant"
import * as puppeteer from "puppeteer"
import { generateInvoiceHtml } from "src/common/emails/templates/trip-invoice"
import { TripLog } from "../../trips/entities/trip-logs.entity"
import { PDFDocument, StandardFonts, rgb } from "pdf-lib"
import archiver from "archiver"
import { TripRepository } from "src/modules/trips/repositories/trip.repository"

@Injectable()
export class InvoicesService {
  constructor(
    private readonly invoiceRepository: InvoiceRepository,
    private readonly clientCompanyRepository: ClientCompanyRepository,
    private readonly customerRepository: CustomerRepository,
    private readonly configService: ConfigService,
    private readonly excelService: ExcelService,
    private readonly tripBasePricingRepository: TripBasePricingRepository,
    private readonly invoiceTripRepository: InvoiceTripRepository,
    private readonly tripAddonsPricingRepository: TripAddOnsPricingRepository,
    private readonly invoiceSettingsRepository: InvoiceSettingsRepository,
    private readonly cityRepository: CityRepository,
    private readonly tripRepository: TripRepository,

    @InjectRepository(Trip)
    private readonly tripEntityRepository: Repository<Trip>,

    @InjectRepository(Invoice)
    private readonly invoiceEntityRepository: Repository<Invoice>,

    @InjectRepository(TripLog)
    private readonly tripLogRepository: Repository<TripLog>,
  ) {}

  async create(createInvoiceDto: CreateInvoiceDto) {
    // -------------------------------
    // 1️⃣ Validate client
    // -------------------------------
    if (!isEmpty(createInvoiceDto?.client_id)) {
      const isClientExist = await this.clientCompanyRepository.getByParams({
        where: { id: createInvoiceDto.client_id },
        findOne: true,
      })

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

    // -------------------------------
    // 1a️⃣ Validate city (if provided)
    // -------------------------------
    if (!isEmpty(createInvoiceDto?.city_id)) {
      const isCityExist = await this.cityRepository.getByParams({
        where: { id: createInvoiceDto.city_id },
        findOne: true,
      })

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

    // -------------------------------
    // 2️⃣ Determine customers to generate invoices for
    // -------------------------------
    const customerIds: number[] = []

    if (!isEmpty(createInvoiceDto?.customer_id)) {
      // Specific customer
      const isCustomerExist = await this.customerRepository.getByParams({
        where: { id: createInvoiceDto.customer_id },
        findOne: true,
      })

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

      customerIds.push(createInvoiceDto?.customer_id)
    } else if (!isEmpty(createInvoiceDto?.client_id)) {
      // All customers of the client
      const customers: any = await this.customerRepository.getByParams({
        where: { client_company_id: createInvoiceDto.client_id },
      })

      if (isEmpty(customers)) {
        return failureResponse(
          code.BAD_REQUEST,
          messageKey.no_invoice_trip_found,
        )
      }

      customerIds.push(...customers.map((c) => c?.id))
    }

    // -------------------------------
    // 3️⃣ Fetch invoice settings
    // -------------------------------
    const invoiceSetting: any =
      await this.invoiceSettingsRepository.getByParams({
        where: { client_id: createInvoiceDto.client_id },
        findOne: true,
      })

    const taxPercentDefault =
      createInvoiceDto?.invoice_tax ?? (invoiceSetting?.client_tax || 0)

    let invoiceCreated = false

    // -------------------------------
    // 4️⃣ Process invoices for each customer
    // -------------------------------
    for (const customerId of customerIds) {
      // Fetch trips not yet invoiced for this customer
      const qb = this.tripEntityRepository
        .createQueryBuilder("trip")
        .where((qb) => {
          const subQuery = qb
            .subQuery()
            .select("invoice_trip.trip_id")
            .from("invoice_trips", "invoice_trip")
            .leftJoin("invoices", "inv", "inv.id = invoice_trip.invoice_id")
            .where("inv.deleted_at IS NULL")
            .getQuery()
          return "trip.id NOT IN " + subQuery
        })

        .andWhere("trip.customer_id = :customerId", { customerId })
        .andWhere("trip.status = :status", { status: STATUS.COMPLETED })

      if (!isEmpty(createInvoiceDto.client_id)) {
        qb.andWhere("trip.client_id = :clientId", {
          clientId: createInvoiceDto.client_id,
        })
      }

      if (createInvoiceDto.start_date && createInvoiceDto.end_date) {
        qb.andWhere("DATE(trip.pickup_datetime) BETWEEN :start AND :end", {
          start: createInvoiceDto.start_date,
          end: createInvoiceDto.end_date,
        })
      }

      if (!isEmpty(createInvoiceDto.city_id)) {
        qb.andWhere("trip.city_id = :cityId", {
          cityId: createInvoiceDto.city_id,
        })
      }

      const trips = await qb
        .leftJoinAndSelect("trip.base_pricing", "base_pricing")
        .leftJoinAndSelect("trip.trip_addons_pricing", "addons_pricing")
        .getMany()

      if (isEmpty(trips)) {
        // Skip this customer if no trips
        continue
      }

      invoiceCreated = true

      // -------------------------------
      // 4a️⃣ Create invoice
      // -------------------------------
      const invoice = new Invoice()
      Object.assign(invoice, { ...createInvoiceDto, customer_id: customerId })
      const savedInvoice = await this.invoiceRepository.save(invoice)

      let totalBasePrice = 0
      let totalAddonsPrice = 0
      // const totalInvoiceAmount = 0
      const invoiceTripsData = []

      // -------------------------------
      // 4b️⃣ Process trips for this invoice
      // -------------------------------
      for (const trip of trips) {
        const { baseTotal, addonsTotal, totalTripPrice } =
          await this.calculateTripTotalPrice(trip.id)

        totalBasePrice += baseTotal
        totalAddonsPrice += addonsTotal
        // totalInvoiceAmount += totalTripPrice

        const invoiceTrip = await this.invoiceTripRepository.save({
          invoice_id: savedInvoice.id,
          trip_id: trip.id,
          total_trip_price: totalTripPrice,
        })

        await this.tripRepository.save(
          { status: STATUS.INVOICED },
          { id: trip?.id },
        )

        invoiceTripsData.push(invoiceTrip)
      }

      // -------------------------------
      // 4c️⃣ Calculate tax, discount, totals
      // -------------------------------
      // const taxAmount = (totalInvoiceAmount * taxPercentDefault) / 100
      // const discountAmount = (totalInvoiceAmount * savedInvoice.discount) / 100

      // savedInvoice.base_price_total = totalBasePrice
      // savedInvoice.addons_price_total = totalAddonsPrice
      // savedInvoice.tax_amount = taxAmount
      // savedInvoice.discount_amount = discountAmount
      // savedInvoice.total_amount =
      //   totalInvoiceAmount + taxAmount - discountAmount

      const subtotal = totalBasePrice + totalAddonsPrice

      const discountAmount = (subtotal * savedInvoice.discount) / 100

      const taxableAmount = subtotal - discountAmount

      const taxAmount = (taxableAmount * taxPercentDefault) / 100

      savedInvoice.base_price_total = totalBasePrice
      savedInvoice.addons_price_total = totalAddonsPrice

      savedInvoice.discount_amount = discountAmount
      savedInvoice.tax_amount = taxAmount
      savedInvoice.total_amount = taxableAmount + taxAmount

      await this.invoiceRepository.save(savedInvoice)
    }

    if (!invoiceCreated) {
      // No trips for any customer → send error
      return failureResponse(code.BAD_REQUEST, messageKey.no_invoice_trip_found)
    }

    // -------------------------------
    // 5️⃣ Return success response
    // -------------------------------
    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_add, { ":data": "Invoice" }),
    )
  }

  /**
   * Calculate total price for a trip (base + add-ons)
   */
  private async calculateTripTotalPrice(tripId: number) {
    const basePricing: any = await this.tripBasePricingRepository.getByParams({
      where: { trip_id: tripId },
    })

    const addOnsPricing: any =
      await this.tripAddonsPricingRepository.getByParams({
        where: { trip_id: tripId },
      })

    const baseTotal = basePricing.reduce(
      (sum, b) =>
        sum +
        Number(b?.vehicle_total_price || 0) +
        Number(b?.meet_greet_price || 0),
      0,
    )

    const addonsTotal = addOnsPricing.reduce(
      (sum, a) => sum + Number(a?.total_price || 0),
      0,
    )

    // return baseTotal + addonsTotal
    const totalTripPrice = baseTotal + addonsTotal

    return { baseTotal, addonsTotal, totalTripPrice }
  }

  async findAll(filterInvoiceDto: FilterInvoiceDto) {
    const defaultPagination = {
      take: this.configService.get<number>("APP.pagination.take"),
      skip: this.configService.get<number>("APP.pagination.skip"),
    }
    const {
      search,
      limit,
      skip,
      client_company_id,
      customer_id,
      status,
      city_id,
    } = filterInvoiceDto || {}

    const where: any = {}

    if (client_company_id) {
      where.client_id = client_company_id
    }

    if (customer_id) {
      where.customer_id = customer_id
    }

    if (filterInvoiceDto?.status) {
      where.status = status
    }

    if (city_id) {
      where.city_id = city_id
    }

    let searchObj: any = {}
    if (search) {
      searchObj = {
        "entity_customer.customer_name": search,
        "entity_client_company.company_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_type",
        "start_date",
        "end_date",
        "due_date",
        "discount",
        "status",
        "base_price_total",
        "addons_price_total",
        "total_amount",
        "payment_received_amount",
        "created_at",
      ],
      relations: [
        "client_company:id,company_name",
        "customer:id,customer_name",
        "city:id,name",
      ],
    }

    const invoice: any = await this.invoiceRepository.getByParams(params)

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

  async excelExport(filterInvoiceDto: FilterInvoiceDto) {
    const { search, client_company_id, customer_id, status, city_id } =
      filterInvoiceDto || {}

    const where: any = {}
    if (client_company_id) {
      where.client_id = client_company_id
    }
    if (customer_id) {
      where.customer_id = customer_id
    }
    if (filterInvoiceDto?.status) {
      where.status = status
    }
    if (city_id) {
      where.city_id = city_id
    }

    let searchObj: any = {}
    if (search) {
      searchObj = {
        "entity_customer.customer_name": search,
        "entity_client_company.company_name": search,
      }
    }

    const params: any = {
      orderBy: { created_at: "DESC" },
      where,
      search: searchObj,
      select: [
        "id",
        "invoice_type",
        "start_date",
        "end_date",
        "due_date",
        "discount",
        "total_amount",
        "payment_received_amount",
        "status",
        "created_at",
      ],
      relations: [
        "client_company:id,company_name",
        "customer:id,customer_name",
      ],
    }

    const invoices: any = await this.invoiceRepository.getByParams(params)

    // Helper function to format date as MM/DD/YYYY
    const formatDate = (dateString: string) => {
      if (!dateString) return ""
      const date = new Date(dateString)
      const month = String(date.getMonth() + 1).padStart(2, "0")
      const day = String(date.getDate()).padStart(2, "0")
      const year = date.getFullYear()
      return `${month}/${day}/${year}`
    }

    // Headers
    const headers = [
      "Invoice ID",
      "Customer Name",
      "Client Name",
      "Invoice Duration",
      "Invoice Date",
      "Due Date",
      "Discount %",
      "Amount",
      "Receivable",
      "Received",
      "Status",
    ]

    // Data rows
    const rows = invoices.map((invoice) => {
      const year = invoice.created_at
        ? new Date(invoice.created_at).getFullYear()
        : ""
      const invoiceId = invoice.id && year ? `INV-${year}-${invoice.id}` : ""

      return [
        invoiceId,
        invoice.customer?.customer_name || "N/A",
        invoice.client_company?.company_name || "",
        invoice.start_date && invoice.end_date
          ? `${formatDate(invoice.start_date)} - ${formatDate(invoice.end_date)}`
          : "",
        formatDate(invoice.created_at),
        formatDate(invoice.due_date),
        `${invoice.discount}%`,
        parseFloat(invoice.total_amount || 0).toFixed(2),
        parseFloat(invoice.due_amount || 0).toFixed(2),
        parseFloat(invoice.payment_received_amount || 0).toFixed(2),
        invoice.status || "",
      ]
    })

    // Combine headers and rows
    const excelData = [headers, ...rows]

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

  async findOne(id: number) {
    const invoice = await this.invoiceEntityRepository
      .createQueryBuilder("invoice")
      .leftJoinAndSelect("invoice.client_company", "client_company")
      .leftJoinAndSelect("invoice.city", "invoice_city")
      .leftJoinAndSelect("client_company.invoice_settings", "invoice_settings")
      .leftJoinAndSelect("invoice.customer", "customer")
      .leftJoinAndSelect("customer.city", "city")
      .leftJoinAndSelect("invoice.invoice_trips", "invoice_trips")
      .leftJoinAndSelect("invoice_trips.trip", "trip")
      .leftJoinAndSelect("trip.plan", "plan")
      .leftJoinAndSelect("plan.charge_type", "charge_type")
      .leftJoinAndSelect("trip.base_pricing", "base_pricing")
      .leftJoinAndSelect("trip.assignments", "assignments")
      .leftJoinAndSelect("assignments.vehicle_type", "vehicle_type")
      // 🏥 Hospital joins
      .leftJoinAndSelect("trip.pickup_hospital", "pickup_hospital")
      .leftJoinAndSelect("trip.dropoff_hospital", "dropoff_hospital")
      .leftJoinAndSelect("trip.customer", "trip_customer")
      .leftJoinAndSelect("trip.addons", "addons")
      .leftJoinAndSelect("trip.trip_type", "trip_type")
      .leftJoinAndSelect("trip.city", "trip_city")
      .where("invoice.id = :id", { id })
      .select([
        // invoice fields
        "invoice.id",
        "invoice.invoice_type",
        "invoice.start_date",
        "invoice.end_date",
        "invoice.due_date",
        "invoice.discount",
        "invoice.status",
        "invoice.notes",
        "invoice.base_price_total",
        "invoice.addons_price_total",
        "invoice.tax_amount",
        "invoice.discount_amount",
        "invoice.total_amount",
        "invoice.payment_received_amount",
        "invoice.invoice_tax",
        "invoice_city.id",
        "invoice_city.name",
        "invoice.created_at",

        // client_company fields
        "client_company.id",
        "client_company.company_name",
        "client_company.address_line_1",
        "client_company.address_line_2",
        "client_company.email",
        "client_company.country_code",
        "client_company.phone_number",

        // invoice_settings fields
        "invoice_settings.id",
        "invoice_settings.client_tax",

        // customer fields
        "customer.id",
        "customer.customer_name",
        "customer.email",
        "customer.primary_address",
        "customer.country_code",
        "customer.phone_number",
        "customer.prn_number",
        "city.id",
        "city.name",

        // invoice_trips fields
        "invoice_trips.id",
        "invoice_trips.invoice_id",
        "invoice_trips.trip_id",
        "invoice_trips.total_trip_price",

        // trip fields
        "trip.id",
        "trip.pickup_datetime",
        "trip.dropoff_datetime",
        "trip.pick_up_location",
        "trip.drop_off_location",
        "trip.pickup_location_type",
        "trip.pickup_hospital_id",
        "trip.dropoff_location_type",
        "trip.dropoff_hospital_id",
        "trip.estimated_time",
        "trip.estimated_distance",
        "trip.appointment_type",
        "trip.trip_timezone",

        "plan.id",
        "plan.name",
        "charge_type.id",
        "charge_type.name",
        "base_pricing.id",
        "base_pricing.vehicle_type_price",

        // trip add ons
        "addons.id",
        "addons.name",

        // assignments fields
        "assignments.id",
        "assignments.fleet_id",
        "assignments.fleet_type_id",

        // vehicle_type fields
        "vehicle_type.id",
        "vehicle_type.name",

        // 🏥 pickup hospital fields
        "pickup_hospital.id",
        "pickup_hospital.name",
        "pickup_hospital.address",

        // 🏥 dropoff hospital fields
        "dropoff_hospital.id",
        "dropoff_hospital.name",
        "dropoff_hospital.address",

        // customer
        "trip_customer.id",
        "trip_customer.customer_name",
        "trip_customer.primary_address",

        // service type
        "trip_type.id",
        "trip_type.name",

        // city
        "trip_city.id",
        "trip_city.name",
      ])
      .getOne()

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

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

  async update(id: number, updateInvoiceDto: UpdateInvoiceDto) {
    const invoice = await this.invoiceRepository.getByParams({
      where: {
        id: id,
      },
      select: [
        "id",
        "invoice_type",
        "start_date",
        "end_date",
        "due_date",
        "discount",
        "status",
        "created_at",
      ],
      relations: [
        "client_company:id,company_name",
        "customer:id,customer_name",
      ],
      findOne: true,
    })

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

    if (!isEmpty(updateInvoiceDto?.client_id)) {
      const isClientExist = await this.clientCompanyRepository.getByParams({
        where: {
          id: updateInvoiceDto?.client_id,
        },
        findOne: true,
      })

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

    if (!isEmpty(updateInvoiceDto?.customer_id)) {
      const isCustomerExist = await this.customerRepository.getByParams({
        where: {
          id: updateInvoiceDto?.customer_id,
        },
        findOne: true,
      })

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

    const invoiceEntity = new Invoice()
    Object.assign(invoiceEntity, updateInvoiceDto)

    const updatedInvoice = await this.invoiceRepository.save(invoiceEntity, {
      id: id,
    })

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

  async remove(id: number) {
    const isInvoiceExist = await this.invoiceRepository.getByParams({
      where: {
        id: id,
      },
      findOne: true,
    })

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

    await this.invoiceTripRepository.remove({ invoice_id: id })

    await this.invoiceRepository.remove({ id: id })

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

  private formatInvoiceDataForExcel(data: any[]) {
    const header = [
      "Invoice ID",
      "Customer Name",
      "Client Name",
      "Invoice Duration",
      "Invoice Date",
      "Due Date",
      "Amount",
      "Status",
    ]

    const rows = data.map((invoice) => [
      invoice?.id,
      invoice?.customer?.customer_name,
      invoice?.client_company?.company_name,
      `${invoice?.start_date} "-" ${invoice?.end_date}`,
      invoice?.created_at,
      invoice?.due_date,
      invoice?.amount || 0,
      invoice?.status,
    ])

    return [header, ...rows]
  }

  async getUniqueLogDocumentsForInvoice(invoiceId: number) {
    const raw = await this.tripLogRepository
      .createQueryBuilder("tl") // trip_logs
      .innerJoin("invoice_trips", "it", "it.trip_id = tl.trip_id") // connect invoice -> trip -> trip_log
      .innerJoin("trips", "t", "t.id = tl.trip_id")
      .leftJoin("episode_logs", "el", "el.id = tl.log_id")
      .where("it.invoice_id = :invoiceId", { invoiceId })
      .andWhere("el.log_document IS NOT NULL")
      .select(["DISTINCT el.id AS id", "el.log_document AS log_document"])
      .getRawMany()

    return raw
  }

  async downloadPdf(url: string): Promise<Buffer> {
    const response = await fetch(url)
    const arrayBuffer = await response.arrayBuffer()
    return Buffer.from(arrayBuffer)
  }

  async generateInvoicePdf(data: any): Promise<any> {
    const browser = await puppeteer.launch({
      headless: true,
      args: ["--no-sandbox", "--disable-setuid-sandbox"],
    })

    try {
      return await this.generateInvoicePdfWithBrowser(data, browser)
    } finally {
      await browser.close()
    }
  }

  private async generateInvoicePdfWithBrowser(
    data: any,
    browser: puppeteer.Browser,
  ): Promise<any> {
    const finalHtml = generateInvoiceHtml(data)

    const page = await browser.newPage()
    await page.setContent(finalHtml, {
      waitUntil: "networkidle0",
      timeout: 60000,
    })

    const pdfBuffer = await page.pdf({
      format: "A4",
      printBackground: true,
      margin: { top: "15mm", bottom: "20mm", left: "0mm", right: "0mm" },
    })

    await page.close()

    const logDocs = await this.getUniqueLogDocumentsForInvoice(data?.id)

    let finalDoc: PDFDocument

    if (!logDocs || logDocs.length === 0) {
      finalDoc = await PDFDocument.load(pdfBuffer, { ignoreEncryption: true })
    } else {
      const mergedPdf = await PDFDocument.create()

      const invoiceDoc = await PDFDocument.load(pdfBuffer, {
        ignoreEncryption: true,
      })
      const invoicePages = await mergedPdf.copyPages(
        invoiceDoc,
        invoiceDoc.getPageIndices(),
      )
      invoicePages.forEach((p) => mergedPdf.addPage(p))

      for (const log of logDocs) {
        const response = await fetch(log.log_document)

        const arrayBuffer = await response.arrayBuffer()
        const logPdf: any = Buffer.from(arrayBuffer)

        const logDoc = await PDFDocument.load(logPdf, {
          ignoreEncryption: true,
        })

        const logPages = await mergedPdf.copyPages(
          logDoc,
          logDoc.getPageIndices(),
        )

        logPages.forEach((page) => mergedPdf.addPage(page))
      }
      finalDoc = mergedPdf
    }

    const totalPages = finalDoc.getPageCount()
    const fontBold = await finalDoc.embedFont(StandardFonts.HelveticaBold)
    const fontRegular = await finalDoc.embedFont(StandardFonts.Helvetica)
    const fontSize = 10

    // Get invoice number from data (already formatted as INV-YYYY-ID)
    const invoiceNumber = data?.invoiceNumber || ""

    for (let i = 0; i < totalPages; i++) {
      const pdfPage = finalDoc.getPage(i)
      const { width } = pdfPage.getSize()

      const footerHeight = 30
      const footerY = 0
      const cutPosition = width * 0.62

      pdfPage.drawRectangle({
        x: 0,
        y: footerY,
        width: cutPosition + 10,
        height: footerHeight,
        color: rgb(0.894, 0.173, 0.18),
      })

      pdfPage.drawRectangle({
        x: cutPosition - 10,
        y: footerY,
        width: width - cutPosition + 10,
        height: footerHeight,
        color: rgb(0.075, 0.235, 0.353),
      })

      const invoiceLabel = "Invoice No:"
      const invoiceLabelWidth = fontBold.widthOfTextAtSize(
        invoiceLabel,
        fontSize,
      )

      pdfPage.drawText(invoiceLabel, {
        x: 15,
        y: footerY + 9,
        size: fontSize,
        font: fontBold,
        color: rgb(1, 1, 1),
      })

      if (invoiceNumber) {
        pdfPage.drawText(invoiceNumber, {
          x: 15 + invoiceLabelWidth + 4,
          y: footerY + 9,
          size: fontSize,
          font: fontRegular,
          color: rgb(1, 1, 1),
        })
      }

      const pageText = `Page ${i + 1} of ${totalPages}`
      const pageTextWidth = fontBold.widthOfTextAtSize(pageText, fontSize)
      pdfPage.drawText(pageText, {
        x: width - pageTextWidth - 15,
        y: footerY + 9,
        size: fontSize,
        font: fontBold,
        color: rgb(1, 1, 1),
      })
    }

    return Buffer.from(await finalDoc.save())
  }

  async updateTripStatusToInvoiced() {
    const invoiceTripsResult = await this.invoiceTripRepository.getByParams({
      relations: ["trip:id,status", "invoice:id,status,deleted_at"],
    })

    let invoiceTrips: any[] = []

    if (Array.isArray(invoiceTripsResult)) {
      invoiceTrips = invoiceTripsResult
    } else if (
      invoiceTripsResult &&
      typeof invoiceTripsResult === "object" &&
      "data" in invoiceTripsResult
    ) {
      invoiceTrips = invoiceTripsResult.data
    }

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

    let updatedCount = 0
    const tripIdsToUpdate: number[] = []

    for (const invoiceTrip of invoiceTrips) {
      if (
        invoiceTrip.trip &&
        invoiceTrip.trip.status !== STATUS.INVOICED &&
        invoiceTrip.invoice &&
        !invoiceTrip.invoice.deleted_at
      ) {
        tripIdsToUpdate.push(invoiceTrip.trip_id)
      }
    }

    if (tripIdsToUpdate.length > 0) {
      await this.tripEntityRepository
        .createQueryBuilder()
        .update(Trip)
        .set({ status: STATUS.INVOICED })
        .where("id IN (:...ids)", { ids: tripIdsToUpdate })
        .execute()

      updatedCount = tripIdsToUpdate.length
    }

    return successResponse(
      code.SUCCESS,
      `Successfully updated ${updatedCount} trip(s) to invoiced status`,
      {
        updated_count: updatedCount,
        trip_ids: tripIdsToUpdate,
      },
    )
  }

  private transformInvoiceDataForPdf(invoiceData: any): any {
    const formatDate = (date: any) => {
      if (!date) return ""
      return new Date(date).toISOString().split("T")[0]
    }

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const formatDateTime = (datetime: any, timezone?: string) => {
      if (!datetime) return ""
      const d = new Date(datetime)
      return d.toLocaleTimeString("en-US", {
        hour: "2-digit",
        minute: "2-digit",
        hour12: true,
      })
    }

    const payload: any = {
      id: invoiceData.id,
      invoiceNumber: `INV-${new Date(invoiceData.created_at).getFullYear()}-${invoiceData.id}`,
      status: invoiceData.status,
      invoiceDate: formatDate(invoiceData.created_at),
      dueDate: invoiceData.due_date,
      period: {
        start: invoiceData.start_date,
        end: invoiceData.end_date,
      },
      notes: invoiceData.notes,
      summary: {
        subtotal: (
          Number(invoiceData?.addons_price_total || 0) +
          Number(invoiceData?.base_price_total || 0)
        ).toFixed(2),
        discountPct: Number(invoiceData.discount || 0).toFixed(2),
        discountAmount: Number(invoiceData.discount_amount || 0).toFixed(2),
        taxAmount: Number(invoiceData.tax_amount || 0).toFixed(2),
        total: Number(invoiceData.total_amount || 0).toFixed(2),
      },
    }

    if (!isEmpty(invoiceData?.customer)) {
      payload.serviceTo = {}

      if (invoiceData.customer.prn_number) {
        payload.serviceTo.service_to_prn = invoiceData.customer.prn_number
      }
      if (invoiceData.customer.customer_name) {
        payload.serviceTo.name = invoiceData.customer.customer_name
      }
      if (invoiceData.customer.email) {
        payload.serviceTo.email = invoiceData.customer.email
      }
      if (invoiceData.customer.phone_number) {
        payload.serviceTo.phoneNumber = [
          invoiceData.customer.country_code,
          invoiceData.customer.phone_number,
        ]
          .filter(Boolean)
          .join(" ")
      }
      if (invoiceData.customer.primary_address) {
        payload.serviceTo.address = invoiceData.customer.primary_address
      }
      if (invoiceData.customer.city?.name) {
        payload.serviceTo.City = invoiceData.customer.city.name
      }
    }

    if (!isEmpty(invoiceData?.client_company)) {
      payload.invoiceTo = {}

      if (invoiceData.client_company.company_name) {
        payload.invoiceTo.name = invoiceData.client_company.company_name
      }
      if (invoiceData.client_company.email) {
        payload.invoiceTo.email = invoiceData.client_company.email
      }
      if (
        invoiceData.client_company.country_code ||
        invoiceData.client_company.phone_number
      ) {
        payload.invoiceTo.phoneNumber = [
          invoiceData.client_company.country_code,
          invoiceData.client_company.phone_number,
        ]
          .filter(Boolean)
          .join(" ")
      }
      if (
        invoiceData.client_company.address_line_1 ||
        invoiceData.client_company.address_line_2
      ) {
        payload.invoiceTo.address = [
          invoiceData.client_company.address_line_1,
          invoiceData.client_company.address_line_2,
        ]
          .filter(Boolean)
          .join(" ")
      }
    }

    payload.trips = invoiceData?.invoice_trips?.map((t: any) => {
      const trip: any = {}

      if (t.trip?.id) {
        trip.trip_id = t.trip.id
      }
      if (t.trip?.pickup_datetime) {
        trip.date = formatDate(t.trip.pickup_datetime)
      }
      if (t.trip?.trip_type?.name) {
        trip.service_type = t.trip.trip_type.name
      }

      trip.pickup =
        t.trip?.pickup_hospital?.name || t.trip?.pick_up_location || ""
      trip.dropoff =
        t.trip?.dropoff_hospital?.name ||
        (t.trip?.dropoff_location_type === "residence"
          ? t.trip?.customer?.primary_address
          : t.trip?.drop_off_location) ||
        ""

      if (t.trip?.pickup_datetime && t.trip?.dropoff_datetime) {
        const pickupTime = formatDateTime(
          t.trip.pickup_datetime,
          t.trip.trip_timezone,
        )
        const dropoffTime = formatDateTime(
          t.trip.dropoff_datetime,
          t.trip.trip_timezone,
        )
        trip.time = `${pickupTime} - ${dropoffTime}`
      }

      if (t.trip?.assignments?.[0]?.vehicle_type?.name) {
        trip.carUsed = t.trip.assignments[0].vehicle_type.name
      }
      if (t.trip?.appointment_type) {
        trip.appointmentType = t.trip.appointment_type
      }
      if (t.trip?.addons && t.trip.addons.length > 0) {
        trip.addOnWithQuantityAndPrice = t.trip.addons.map(
          (addon: any) => addon.name,
        )
      }
      if (t.total_trip_price !== undefined && t.total_trip_price !== null) {
        trip.amount = Number(t.total_trip_price)
      }
      if (t.trip?.plan?.charge_type?.name) {
        trip.charge_type = t.trip.plan.charge_type.name
      }
      if (t.trip?.base_pricing?.[0]?.vehicle_type_price) {
        trip.base_price = t.trip.base_pricing[0].vehicle_type_price
      }

      return trip
    })

    return payload
  }

  async bulkDownloadInvoices(invoiceIds: number[], res: any): Promise<void> {
    const archive = archiver("zip", {
      zlib: { level: 9 },
    })

    const timestamp = new Date().toISOString().split("T")[0]
    const filename = `invoices_${timestamp}.zip`

    res.set({
      "Content-Type": "application/zip",
      "Content-Disposition": `attachment; filename="${filename}"`,
    })

    archive.on("error", (err) => {
      console.error("Archive error:", err)
      throw err
    })

    archive.on("warning", (err) => {
      if (err.code === "ENOENT") {
        console.warn("Archive warning:", err)
      } else {
        throw err
      }
    })

    archive.pipe(res)

    let browser: puppeteer.Browser | null = null

    try {
      browser = await puppeteer.launch({
        headless: true,
        args: ["--no-sandbox", "--disable-setuid-sandbox"],
      })

      const results = []
      const BATCH_SIZE = 5

      for (let i = 0; i < invoiceIds.length; i += BATCH_SIZE) {
        const batch = invoiceIds.slice(i, i + BATCH_SIZE)

        const batchPromises = batch.map(async (invoiceId) => {
          try {
            const invoiceResponse: any = await this.findOne(invoiceId)

            if (
              !invoiceResponse ||
              invoiceResponse.code !== code.SUCCESS ||
              isEmpty(invoiceResponse.data)
            ) {
              console.error(
                `Invoice with ID ${invoiceId} not found, skipping...`,
              )
              return null
            }

            const rawInvoiceData = invoiceResponse.data
            const transformedData =
              this.transformInvoiceDataForPdf(rawInvoiceData)
            const pdfBuffer = await this.generateInvoicePdfWithBrowser(
              transformedData,
              browser,
            )

            return {
              buffer: pdfBuffer,
              filename: `${transformedData.invoiceNumber}.pdf`,
            }
          } catch (error) {
            console.error(
              `Error generating PDF for invoice ${invoiceId}:`,
              error.message,
            )
            return null
          }
        })

        const batchResults = await Promise.all(batchPromises)
        results.push(...batchResults)
      }

      for (const result of results) {
        if (result) {
          archive.append(result.buffer, {
            name: result.filename,
          })
        }
      }
    } finally {
      if (browser) {
        await browser.close()
      }
    }

    await archive.finalize()
  }
}
