import { Injectable, Logger } from "@nestjs/common"
import { ConfigService } from "@nestjs/config"
import { InjectRepository } from "@nestjs/typeorm"
import { failureResponse, successResponse } from "src/common/response/response"
import { code } from "src/common/response/response.code"
import { messageKey } from "src/constants/message-keys"
import {
  formatMessage,
  customerMessages,
} from "src/constants/notification-message"
import { STATUS } from "src/constants/trip.constant"
import {
  addOnsEnum,
  convertUtcToTimezone,
  dutyStatus,
  errorMessage,
  formateDate,
  getCityFromLatLngBackend,
  getTimezone,
  isEmpty,
  successMessage,
  tripTimeZone,
  validationMessage,
} from "src/utils/helpers"
import { Brackets, Repository } from "typeorm"
import { AddOnRepository } from "../../add-ons/repositories/add-ons.repository"
import { ClientCompanyContactRepository } from "../../client-company-contacts/repositories/client-company-contacts.repository"
import { ClientCompanyRepository } from "../../clients-companies/repositories/clients-companies.repository"
import { CustomerRepository } from "../../customers/repositories/customers.repository"
import { Escort } from "../../escorts/entities/escort.entity"
import { EscortRepository } from "../../escorts/repositories/escort.repository"
import { FleetManagementService } from "../../fleet-management/v1/fleet-management.service"
import { FleetManagementRepository } from "../../fleet-management/repositories/fleet-management.repository"
import { HospitalRepository } from "../../hospitals/repositories/hospital.repository"
import { NotificationService } from "../../notification/v1/notification.service"
import { TeamMember } from "../../team-member/entities/team_member.entity"
import { UpdateTripBabySeatDto } from "../dto/assign-baby-seat.dto"
import {
  CanceledBy,
  CreateTripCancellationDto,
} from "../dto/create-trip-cancellation.dto"
import { CreateTripDto } from "../dto/create-trip.dto"
import { TripFilterDto } from "../dto/filter-trip.dto"
import { AssignFleetsToTripDto } from "../dto/fleet-assignment.dto"
import { PaginationDto } from "../dto/pagination.dto"
import { UpdateTripCancellationDto } from "../dto/update-trip-cancellation.dto"
import { UpdateTripDto } from "../dto/update-trip.dto"
import { TripFleetAssignment } from "../entities/fleet-trip-management.entity"
import { TripBabySeat } from "../entities/trip-baby-seat.entity"
import { TripCancellation } from "../entities/trip-cancellations.entity"
import { TripType } from "../entities/trip-type.entity"
import { Trip } from "../entities/trip.entity"
import { TripBabySeatRepository } from "../repositories/trip-baby-repository"
import { TripCancelRepository } from "../repositories/trip-cancel-repository"
import { TripFleetRepository } from "../repositories/trip-fleet-repository"
import { TripTypeRepository } from "../repositories/trip-type.repository"
import { TripRepository } from "../repositories/trip.repository"
import { UserLoginRepository } from "../../auth/repositories/user-login.repository"
import { CityRepository } from "../../city/repositories/city.repository"
import { FleetManagement } from "../../fleet-management/entities/fleet-management.entity"
import { VEHICLE_STATUS } from "../../vehicle-status/entities/vehicle-status.entity"
import { VehicleStatusService } from "../../vehicle-status/v1/vehicle-status.service"
import { PricingPlanRepository } from "../../plans/repositories/plan.repository"
import { ClientCompanyContractRepository } from "../../client-contract/repositories/client-contarct.repository"
import moment from "moment"
import { AuthService } from "../../auth/v1/auth.service"
import { TripAddOnsPricingDto } from "../dto/trip-addons-pricing.dto"
import { TripAddOnsPricingRepository } from "../repositories/trip-addons-pricing.repository"
import { TripServicePricingRepository } from "../repositories/trips-service-pricing.repository"
import { TripServicePricingDto } from "../dto/trip-service-pricing.dto"
import { Customer } from "../../customers/entities/customer.entity"
import { TripBasePricingRepository } from "../repositories/trip-base-price.repository"
import { VehiclePricing } from "../../vehicle-pricing/entities/vehicle-pricing.entity"
import { MeetAndGreetPricing } from "../../meet-greet-pricing/entities/meet-greet-pricing.entity"
import { TripBasePricing } from "../entities/trip-base-pricing.entity"
import { TripPricingDto } from "../dto/trip-base-pricing.dto"
import { CreateTripTrackingDto } from "../dto/trip-tracking.dto"
import { TripTrackingRepository } from "../repositories/trip-tracking.repository"
import { TripTracking } from "../entities/trip-tracking.entity"
import { CreateTripRequestByCustomerDto } from "../dto/create-trip-request-by-customer.dto"
import { TeamMemberRepository } from "../../team-member/repositories/team_member.repository"
import { RatingRepository } from "../../rating/repositories/rating.repository"
import { DriverFleetHistoryRepository } from "../../fleet-management/repositories/driver-fleet-history.repository"
import { RATING_TYPES } from "../../rating/constants/rating-type.constants"
import { TripLogsRepository } from "../repositories/trip-logs.repository"
import { TripLog } from "../entities/trip-logs.entity"
import { InvoiceTripRepository } from "../../invoices/repositories/trip-invoice.repository"
import { TripTimelineRepository } from "../repositories/trip-timeline.repository"
import { UpdateTripTypeDto } from "../dto/trip-type.dto"
import { TripTimelineHistory } from "../entities/trip-timeline.entity"
import { CompleteTripDto } from "../dto/complete-trip.dto"
import { StateRepository } from "src/modules/state/repositories/state.repository"
import { CountryRepository } from "src/modules/country/repositories/country.repository"
import { AddOnsPricing } from "src/modules/add-ons-pricing/entities/add-ons-pricing.entity"

@Injectable()
export class TripsService {
  private readonly logger = new Logger(TripsService.name)

  constructor(
    private readonly tripTypeRepository: TripTypeRepository,
    private readonly tripRepository: TripRepository,
    private readonly clientCompanyRepository: ClientCompanyRepository,
    private readonly clientCompanyContactRepository: ClientCompanyContactRepository,
    private readonly customerRepository: CustomerRepository,
    private readonly hospitalRepository: HospitalRepository,
    private readonly configService: ConfigService,
    private readonly addOnRepository: AddOnRepository,
    private readonly tripFleetRepository: TripFleetRepository,
    private readonly fleetManagementService: FleetManagementService,
    private readonly vehicleStatusService: VehicleStatusService,
    private readonly tripBabySeatRepository: TripBabySeatRepository,
    private readonly fleetManagementRepository: FleetManagementRepository,
    private readonly escortRepository: EscortRepository,
    private readonly tripCancelRepository: TripCancelRepository,
    private readonly notificationService: NotificationService,
    private readonly userLoginRepository: UserLoginRepository,
    private readonly cityRepository: CityRepository,
    private readonly planRepository: PricingPlanRepository,
    private readonly clientContractRepository: ClientCompanyContractRepository,
    private readonly authService: AuthService,
    private readonly tripAddOnsPricingRepository: TripAddOnsPricingRepository,
    private readonly tripServicePricingRepository: TripServicePricingRepository,
    private readonly tripBasePricingRepository: TripBasePricingRepository,
    private readonly tripTrackingRepository: TripTrackingRepository,
    private readonly teamMemberRepository: TeamMemberRepository,
    private readonly ratingRepository: RatingRepository,
    private readonly driverFleetHistoryRepository: DriverFleetHistoryRepository,
    private readonly tripLogsRepository: TripLogsRepository,
    private readonly invoiceTripRepository: InvoiceTripRepository,
    private readonly tripTimelineHistoryRepository: TripTimelineRepository,
    private readonly countryRepository: CountryRepository,
    private readonly stateRepository: StateRepository,

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

    @InjectRepository(FleetManagement)
    private readonly fleetManagementEntityRepository: Repository<FleetManagement>,

    @InjectRepository(Escort)
    private readonly escortEntityRepository: Repository<Escort>,

    @InjectRepository(TeamMember)
    private readonly teamMemberEntityRepository: Repository<TeamMember>,

    @InjectRepository(VehiclePricing)
    private readonly vehiclePricingEntityRepository: Repository<VehiclePricing>,

    @InjectRepository(MeetAndGreetPricing)
    private readonly meetAndGreetEntityRepository: Repository<MeetAndGreetPricing>,

    @InjectRepository(TripFleetAssignment)
    private readonly tripFleetAssignmentRepository: Repository<TripFleetAssignment>,

    @InjectRepository(AddOnsPricing)
    private readonly addOnsPricingEntityRepository: Repository<AddOnsPricing>,
  ) {}

  async create(
    createTripDto: CreateTripDto,
    tripSupportDocument: Express.Multer.File,
  ) {
    if (!isEmpty(createTripDto?.client_id)) {
      const isClientExist = await this.clientCompanyRepository.getByParams({
        where: {
          id: createTripDto?.client_id,
        },
        findOne: true,
      })

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

    if (!isEmpty(createTripDto?.client_contact_billing_id)) {
      const isClientBillingExist =
        await this.clientCompanyContactRepository.getByParams({
          where: { id: createTripDto?.client_contact_billing_id },
          findOne: true,
        })

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

    if (!isEmpty(createTripDto?.client_booking_id)) {
      const isClientBooking =
        await this.clientCompanyContactRepository.getByParams({
          where: {
            id: createTripDto?.client_booking_id,
          },
          findOne: true,
        })

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

    const isCustomerExist: any = await this.customerRepository.getByParams({
      where: {
        id: createTripDto?.customer_id,
      },
      findOne: true,
    })

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

    if (tripSupportDocument) {
      const relativePath = tripSupportDocument?.path
        .replace(/\\/g, "/")
        .replace(/^public\//, "")
      createTripDto.trip_document = relativePath

      createTripDto.document_type = tripSupportDocument.mimetype?.split("/")[0]
    }

    const trip = new Trip()

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const { episode_id, log_id, ...tripPayload } = createTripDto

    Object.assign(trip, tripPayload)

    trip.city_id = isCustomerExist?.pricing_city_id

    if (isEmpty(createTripDto.client_id)) {
      const defaultPlan: any = await this.planRepository.getByParams({
        where: { is_default: true },
        findOne: true,
      })

      if (isEmpty(defaultPlan)) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, { ":data": "Default Plan" }),
        )
      }

      trip.plan_id = defaultPlan.id
    } else if (createTripDto?.client_id) {
      const now = moment().toDate()

      const activeContract: any =
        await this.clientContractRepository.getByParams({
          where: {
            client_company_id: createTripDto.client_id,
            status: "active",
            start_date: { lte: now },
            end_date: { gte: now },
          },
          findOne: true,
        })

      if (isEmpty(activeContract)) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, {
            ":data": "Active Contract",
          }),
        )
      }

      trip.plan_id = activeContract.payment_plan_id
    }

    const tripData = await this.tripRepository.save(trip)

    if (
      !isEmpty(createTripDto?.episode_id) &&
      !isEmpty(createTripDto?.log_id)
    ) {
      const tripLogs = new TripLog()

      Object.assign(tripLogs, {
        trip_id: tripData?.id,
        episode_id: createTripDto?.episode_id,
        log_id: createTripDto?.log_id,
      })

      await this.tripLogsRepository.save(tripLogs)
    }

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

  async findAll(
    token: any,
    filterTripDto: TripFilterDto,
    excludeActiveStatus = false,
    isCustomer: boolean = false,
    deviceType?: string,
  ) {
    const limit =
      filterTripDto.limit ||
      this.configService.get<number>("APP.pagination.take")
    const skip =
      filterTripDto.skip ||
      this.configService.get<number>("APP.pagination.skip")

    const userDetail: any = await this.userLoginRepository.getByParams({
      where: {
        access_token: token,
      },
      findOne: true,
      select: ["id", "user_id"],
      relations: ["user:id,team_member_id,role_id.role:id,name"],
    })

    const search = filterTripDto.search || ""
    const tripTypeId = filterTripDto.trip_type_id || null
    const tripStatus = filterTripDto.status || null
    const driverId = filterTripDto.driver_id || null
    const customerId = filterTripDto?.customer_id || null
    const orderBy = filterTripDto.order_by || "trip.id"
    const order = filterTripDto.order || "DESC"
    const fromDate = filterTripDto?.from_date || null

    const query = this.tripEntityRepository
      .createQueryBuilder("trip")
      .leftJoinAndSelect("trip.customer", "customer")
      .leftJoinAndSelect("customer.dispatcher", "dispatcher")
      .leftJoinAndSelect("trip.trip_type", "trip_type")
      .leftJoinAndSelect("trip.pickup_hospital", "pickup_hospital")
      .leftJoinAndSelect("trip.dropoff_hospital", "dropoff_hospital")
      .leftJoinAndSelect("trip.assignments", "assignments")
      .leftJoinAndSelect("assignments.driver", "driver")
      .leftJoinAndSelect("assignments.fleet", "fleet")
      .leftJoinAndSelect("trip.cancellations", "cancellations")
      .leftJoinAndSelect("cancellations.reason", "reason")
      .leftJoinAndSelect("fleet.vehicleModel", "vehicle_model")
      .leftJoinAndSelect("fleet.vehicle_type", "vehicle_type")
      .leftJoinAndSelect("trip.baby_seats", "baby_seats")
      .leftJoinAndSelect("trip.addons", "addons")

      .select([
        "trip.id",
        "trip.customer_id",
        "trip.trip_type_id",
        "trip.status",
        "trip.pickup_datetime",
        "trip.dropoff_datetime",
        "trip.trip_timezone",
        "trip.pickup_hospital_id",
        "trip.dropoff_hospital_id",
        "trip.pick_up_location",
        "trip.pickup_location_type",
        "trip.dropoff_location_type",
        "trip.drop_off_location",
        "trip.pickup_place_id",
        "trip.dropoff_place_id",
        "trip.created_at",
        "customer.id",
        "customer.customer_name",
        "customer.primary_address",
        "customer.latitude",
        "customer.longitude",
        "trip_type.id",
        "trip_type.name",
        "pickup_hospital.id",
        "pickup_hospital.name",
        "pickup_hospital.address",
        "dropoff_hospital.id",
        "dropoff_hospital.name",
        "dropoff_hospital.address",
        "assignments.id",
        "driver.id",
        "driver.first_name",
        "driver.last_name",
        "fleet.id",
        "vehicle_model.id",
        "vehicle_model.name",
        "vehicle_type.id",
        "vehicle_type.name",
        "baby_seats.id",
        "baby_seats.trip_id",
        "baby_seats.addon_id",
        "baby_seats.direction",
        "addons.id",
        "addons.name",
        "addons.description",
        "dispatcher.id",
        "cancellations.other_input",
        "cancellations.canceled_by",
        "cancellations.canceled_by_id",
        "reason.name",
      ])

    if (excludeActiveStatus) {
      const datePriorityExpr = `CASE WHEN trip.pickup_datetime::date = CURRENT_DATE THEN 1 WHEN trip.pickup_datetime::date > CURRENT_DATE THEN 2 ELSE 3 END`
      query
        .addSelect(datePriorityExpr, "date_priority")
        .addSelect(
          `CASE WHEN (${datePriorityExpr}) = 3 THEN trip.pickup_datetime END`,
          "sort_past",
        )
        .addSelect(
          `CASE WHEN (${datePriorityExpr}) IN (1, 2) THEN trip.pickup_datetime END`,
          "sort_future_today",
        )

      if (isCustomer) {
        // ongoing/pickup on top, then date priority for customer app
        query
          .addSelect(
            `CASE WHEN trip.status IN ('ongoing', 'pickup') THEN 0 ELSE 1 END`,
            "status_priority",
          )
          .orderBy("status_priority", "ASC")
          .addOrderBy("date_priority", "ASC")
          .addOrderBy("sort_past", "DESC", "NULLS LAST")
          .addOrderBy("sort_future_today", "ASC", "NULLS LAST")
      } else {
        query
          .orderBy("date_priority", "ASC")
          .addOrderBy("sort_past", "DESC", "NULLS LAST")
          .addOrderBy("sort_future_today", "ASC", "NULLS LAST")
      }
    } else {
      query.orderBy(orderBy, order as "ASC" | "DESC")
    }
    query.skip(skip).take(limit)

    // filter by from date
    if (fromDate) {
      query.andWhere("DATE(trip.pickup_datetime) >= DATE(:fromDate)", {
        fromDate,
      })
    }

    // filter by trip type
    if (tripTypeId) {
      query.andWhere("trip.trip_type_id = :tripTypeId", { tripTypeId })
    }

    // filter by trip status
    if (tripStatus) {
      const statusArray =
        typeof tripStatus === "string"
          ? tripStatus.split(",").map((s) => s.trim())
          : tripStatus

      query.andWhere("trip.status IN (:...statusArray)", { statusArray })
    }

    if (driverId) {
      query.andWhere("assignments.driver_id = :driverId", { driverId })
    }

    if (customerId) {
      query.andWhere("trip.customer_id = :customerId", { customerId })
    }

    if (userDetail?.user?.role?.name.toLowerCase() === "dispatcher") {
      query.andWhere("dispatcher.id = :dispatcherId", {
        dispatcherId: userDetail.user?.team_member_id,
      })
    }
    // search by customer name
    if (search) {
      query.andWhere("customer.customer_name ILIKE :search", {
        search: `%${search}%`,
      })
    }

    if (search) {
      query.orWhere("trip.id::text ILIKE :search", { search: `%${search}%` })
    }

    if (search) {
      query.orWhere("trip.prn_number ILIKE :search", {
        search: `%${search}%`,
      })
    }

    if (excludeActiveStatus) {
      query.andWhere("trip.status != :activeStatus", { activeStatus: "active" })
    }

    if (deviceType) {
      const normalizedDeviceType = deviceType.toLowerCase()
      if (
        normalizedDeviceType === "android" ||
        normalizedDeviceType === "ios"
      ) {
        query.andWhere("trip.status != :invoicedStatus", {
          invoicedStatus: STATUS.INVOICED,
        })
      }
    }

    const [trips, count] = await query.getManyAndCount()

    if (trips.length > 0) {
      // Collect all cancellations
      const allCancellations = trips.flatMap((trip) => trip.cancellations || [])
      const customerCancellations = allCancellations.filter(
        (c) => c.canceled_by === CanceledBy.CUSTOMER && c.canceled_by_id,
      )
      const teamMemberCancellations = allCancellations.filter(
        (c) => c.canceled_by !== CanceledBy.CUSTOMER && c.canceled_by_id,
      )

      if (customerCancellations.length) {
        const customerIds = [
          ...new Set(customerCancellations.map((c) => c.canceled_by_id)),
        ]
        const customers = (await this.customerRepository.getByParams({
          whereIn: { id: customerIds },
          select: ["id", "customer_name"],
        })) as Customer[]

        customerCancellations.forEach((c) => {
          const customer = customers.find(
            (cust) => cust.id === c.canceled_by_id,
          )
          if (customer) {
            ;(c as any).canceled_by_name = customer.customer_name
          }
        })
      }

      if (teamMemberCancellations.length) {
        const teamMemberIds = [
          ...new Set(teamMemberCancellations.map((c) => c.canceled_by_id)),
        ]
        const teamMembers = (await this.teamMemberRepository.getByParams({
          whereIn: { id: teamMemberIds },
          select: ["id", "first_name", "last_name"],
        })) as any[]

        teamMemberCancellations.forEach((c) => {
          const member = teamMembers.find((m) => m.id === c.canceled_by_id)
          if (member) {
            ;(c as any).canceled_by_name =
              `${member.first_name} ${member.last_name}`.trim()
          }
        })
      }
    }

    return successResponse(
      code.SUCCESS,
      isCustomer
        ? messageKey.trip_retrieved
        : successMessage(messageKey.data_retrieve, { ":data": "Trips" }),
      {
        count,
        data: trips,
      },
    )
  }

  async findOne(id: number, isCustomer: boolean) {
    const tripManagement = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .leftJoinAndSelect("trip.customer", "customer")
      .leftJoinAndSelect("customer.city", "customer_city")
      .leftJoinAndSelect("customer.state", "customer_state")
      .leftJoinAndSelect("customer.country", "customer_country")
      .leftJoinAndSelect(
        "trip.client_contact_billing",
        "client_contact_billing",
      )
      .leftJoinAndSelect("trip.client_booking", "client_booking")
      .leftJoinAndSelect("trip.trip_type", "trip_type")
      .leftJoinAndSelect("trip.pickup_hospital", "pickup_hospital")
      .leftJoinAndSelect("pickup_hospital.city", "pickup_hospital_city")
      .leftJoinAndSelect("pickup_hospital.state", "pickup_hospital_state")
      .leftJoinAndSelect("trip.dropoff_hospital", "dropoff_hospital")
      .leftJoinAndSelect("dropoff_hospital.city", "dropoff_hospital_city")
      .leftJoinAndSelect("dropoff_hospital.state", "dropoff_hospital_state")
      .leftJoinAndSelect("trip.client", "client")
      .leftJoinAndSelect("trip.cancellations", "cancellations")
      .leftJoinAndSelect("cancellations.reason", "reason")
      .leftJoinAndSelect("trip.intermediate_stop", "intermediate_stop")
      .leftJoinAndSelect(
        "intermediate_stop.hospital",
        "intermediate_stop_hospital",
      )
      .leftJoinAndSelect(
        "intermediate_stop_hospital.city",
        "intermediate_stop_hospital_city",
      )
      .leftJoinAndSelect(
        "intermediate_stop_hospital.state",
        "intermediate_stop_hospital_state",
      )
      .leftJoinAndSelect("trip.assignments", "assignments")
      .leftJoinAndSelect("assignments.driver", "driver")
      .leftJoinAndSelect("driver.addresses", "driver_address")
      .leftJoinAndSelect("driver.languages", "languages")
      .leftJoinAndSelect("assignments.fleet", "fleet")
      .leftJoinAndSelect("assignments.vehicle_type", "fleet_vehicle_type")
      .leftJoinAndSelect("fleet.vehicleModel", "vehicle_model")
      .leftJoinAndSelect("fleet.vehicle_type", "vehicle_type")
      .leftJoinAndSelect("fleet.vehicle_manufacture", "vehicle_manufacture")
      .leftJoinAndSelect("trip.baby_seats", "baby_seats")
      .leftJoinAndSelect("trip.addons", "addons")
      .leftJoinAndSelect("trip.trip_logs", "trip_logs")
      .leftJoinAndSelect("trip_logs.episode_log", "episode_log")
      .leftJoinAndSelect("episode_log.hospital", "hospital")
      .leftJoinAndSelect("trip.city", "trip_city")
      .leftJoinAndSelect("trip_city.state", "trip_city_state")
      .leftJoinAndSelect("trip_city.country", "trip_city_country")
      .where("trip.id = :id", { id })
      .select([
        "trip",
        "customer.id",
        "customer.customer_name",
        "customer.primary_address",
        "customer.secondary_address",
        "customer.zip_code",
        "customer.gender",
        "customer.date_of_birth",
        "customer.phone_number",
        "customer.country_code",
        "customer.latitude",
        "customer.longitude",
        "customer.profile_photo",
        "customer_city.id",
        "customer_city.name",
        "customer_state.id",
        "customer_state.name",
        "customer_country.id",
        "customer_country.name",
        "client_contact_billing.id",
        "client_contact_billing.first_name",
        "client_contact_billing.last_name",
        "client_booking.id",
        "client_booking.first_name",
        "client_booking.last_name",
        "trip_type.id",
        "trip_type.name",
        "trip_type.type",
        "pickup_hospital.id",
        "pickup_hospital.name",
        "pickup_hospital.address",
        "pickup_hospital.latitude",
        "pickup_hospital.longitude",
        "pickup_hospital_city.id",
        "pickup_hospital_city.name",
        "pickup_hospital_state.id",
        "pickup_hospital_state.name",
        "dropoff_hospital.id",
        "dropoff_hospital.name",
        "dropoff_hospital.address",
        "dropoff_hospital.latitude",
        "dropoff_hospital.longitude",
        "dropoff_hospital_city.id",
        "dropoff_hospital_city.name",
        "dropoff_hospital_state.id",
        "dropoff_hospital_state.name",
        "client.id",
        "client.company_name",
        "client.is_medical_patient",
        "intermediate_stop.id",
        "intermediate_stop.stop_address",
        "intermediate_stop.duration",
        "intermediate_stop.notes",
        "intermediate_stop.type",
        "intermediate_stop.hospital_id",
        "intermediate_stop_hospital.id",
        "intermediate_stop_hospital.name",
        "intermediate_stop_hospital.address",
        "intermediate_stop.latitude",
        "intermediate_stop.longitude",
        "intermediate_stop.estimated_time",
        "intermediate_stop.estimated_distance",
        "intermediate_stop_hospital.latitude",
        "intermediate_stop_hospital.longitude",
        "intermediate_stop_hospital_city.id",
        "intermediate_stop_hospital_city.name",
        "intermediate_stop_hospital_state.id",
        "intermediate_stop_hospital_state.name",
        "assignments.id",
        "assignments.fleet_type_id",
        "driver.id",
        "driver.first_name",
        "driver.last_name",
        "driver.country_code",
        "driver.phone_number",
        "driver.profile_photo",
        "driver_address.address_line_1",
        "driver_address.address_line_2",
        "driver_address.city",
        "driver_address.state",
        "driver_address.zipcode",
        "languages.id",
        "languages.code",
        "languages.name",
        "fleet.id",
        "fleet.car_code",
        "fleet.year",
        "fleet.color",
        "fleet.registration_number",
        "fleet.assigned_driver",
        "vehicle_model.id",
        "vehicle_model.name",
        "vehicle_type.id",
        "vehicle_type.name",
        "vehicle_manufacture.id",
        "vehicle_manufacture.name",
        "addons.id",
        "addons.name",
        "baby_seats.id",
        "baby_seats.direction",
        "fleet_vehicle_type.id",
        "fleet_vehicle_type.name",
        "cancellations.other_input",
        "cancellations.canceled_by",
        "cancellations.canceled_by_id",
        "reason.name",
        "trip_logs.id",
        "trip_logs.episode_id",
        "trip_logs.log_id",
        "episode_log.id",
        "episode_log.log_number",
        "episode_log.reference_number",
        "episode_log.log_document",
        "episode_log.created_at",
        "hospital.id",
        "hospital.name",
        "trip_city.id",
        "trip_city.name",
        "trip_city_state.id",
        "trip_city_state.name",
        "trip_city_country.id",
        "trip_city_country.name",
      ])
      .getOne()

    if (isEmpty(tripManagement)) {
      return failureResponse(
        code.VALIDATION,
        isCustomer
          ? messageKey.trip_not_found
          : errorMessage(messageKey.data_not_found, {
              ":data": "Trip",
            }),
      )
    }

    if (tripManagement.cancellations?.length) {
      const customerCancellations = tripManagement.cancellations.filter(
        (c) => c.canceled_by === CanceledBy.CUSTOMER && c.canceled_by_id,
      )
      const teamMemberCancellations = tripManagement.cancellations.filter(
        (c) => c.canceled_by !== CanceledBy.CUSTOMER && c.canceled_by_id,
      )

      if (customerCancellations.length) {
        const customerIds = customerCancellations.map((c) => c.canceled_by_id)
        const customers = (await this.customerRepository.getByParams({
          whereIn: { id: customerIds },
          select: ["id", "customer_name"],
        })) as Customer[]

        customerCancellations.forEach((c: any) => {
          const customer = customers.find(
            (cust) => cust.id === c.canceled_by_id,
          )
          if (customer) {
            c.canceled_by_info = {
              first_name: customer.customer_name,
              last_name: "",
            }
          }
        })
      }

      if (teamMemberCancellations.length) {
        const teamMemberIds = teamMemberCancellations.map(
          (c) => c.canceled_by_id,
        )
        const teamMembers = (await this.teamMemberRepository.getByParams({
          whereIn: { id: teamMemberIds },
          select: ["id", "first_name", "last_name"],
        })) as any[]

        teamMemberCancellations.forEach((c: any) => {
          const member = teamMembers.find((m) => m.id === c.canceled_by_id)
          if (member) {
            c.canceled_by_info = {
              first_name: member.first_name,
              last_name: member.last_name,
            }
          }
        })
      }
    }

    const isTripInvoiced = await this.invoiceTripRepository.getByParams({
      where: {
        trip_id: id,
      },
      findOne: true,
    })

    const customerEscortsRaw = await this.escortEntityRepository
      .createQueryBuilder("escort")
      .leftJoin("escort.customer", "customer")
      .leftJoin(
        "trip_escorts",
        "trip_escort",
        "trip_escort.escort_id = escort.id AND trip_escort.trip_id = :tripId",
        { tripId: id },
      )
      .where("customer.id = :customerId", {
        customerId: tripManagement.customer.id,
      })
      .select([
        "escort.id AS id",
        "escort.name AS name",
        "escort.gender AS gender",
        "escort.date_of_birth AS date_of_birth",
        "escort.phone_number AS phone_number",
        "escort.country_code AS country_code",
        "CASE WHEN trip_escort.trip_id IS NOT NULL THEN true ELSE false END AS is_selected",
      ])
      .orderBy("escort.created_at", "DESC")
      .getRawMany()

    const customerEscorts = customerEscortsRaw.map((e) => ({
      id: e.id,
      name: e.name,
      gender: e.gender,
      date_of_birth: e.date_of_birth,
      phone_number: e.phone_number,
      country_code: e.country_code,
      is_selected: e.is_selected === true || e.is_selected === "true",
    }))

    const tripRatings = await this.ratingRepository.getRatingForTripId(id)

    return successResponse(code.SUCCESS, messageKey.data_retrieve, {
      ...tripManagement,
      customer_escorts: customerEscorts,
      ratings: tripRatings,
      is_trip_invoiced: !isEmpty(isTripInvoiced),
    })
  }

  async update(
    id: number,
    updateTripDto: UpdateTripDto,
    tripSupportDocument?: Express.Multer.File,
    userId?: number,
    teamMemberId?: number,
  ) {
    const tripData: any = await this.tripRepository.getByParams({
      where: {
        id: id,
      },
      findOne: true,
    })
    // const existingTripData = JSON.parse(JSON.stringify(tripData))

    if (isEmpty(tripData)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip",
        }),
      )
    }

    const previousStatus = tripData?.status
    const previousCityId = tripData?.city_id
    const previousTripTypeId = tripData?.trip_type_id

    if (updateTripDto?.current_step === 1) {
      if (!updateTripDto?.is_direct_customer) {
        const isClientExist = await this.clientCompanyRepository.getByParams({
          where: {
            id: updateTripDto?.client_id,
          },
          findOne: true,
        })

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

        const isClientBillingExist =
          await this.clientCompanyContactRepository.getByParams({
            where: { id: updateTripDto?.client_contact_billing_id },
            findOne: true,
          })

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

        const isClientBooking =
          await this.clientCompanyContactRepository.getByParams({
            where: {
              id: updateTripDto?.client_booking_id,
            },
            findOne: true,
          })

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

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

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

      if (tripSupportDocument) {
        const relativePath = tripSupportDocument.path
          .replace(/\\/g, "/")
          .replace(/^public\//, "")
        updateTripDto.trip_document = relativePath
        updateTripDto.document_type =
          tripSupportDocument.mimetype?.split("/")[0]
      }
    } else if (updateTripDto?.current_step === 2) {
      const tripType = await this.tripTypeRepository.getByParams({
        where: {
          id: updateTripDto?.trip_type_id,
        },
        findOne: true,
      })

      if (isEmpty(tripType)) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, { ":data": "Trip Type" }),
        )
      }

      if (updateTripDto?.pickup_location_type === "residence") {
        const customerData: any = await this.customerRepository.getByParams({
          where: {
            id: tripData.customer_id,
          },
          findOne: true,
          select: [
            "id",
            "latitude",
            "longitude",
            "place_id",
            "primary_address",
            "secondary_address",
            "city",
            "state",
            "country",
            "zip_code",
          ],
        })

        if (isEmpty(customerData)) {
          return failureResponse(
            code.DATA_NOT_FOUND,
            errorMessage(messageKey.data_not_found, { ":data": "Customer" }),
          )
        }
        updateTripDto.pickup_latitude = customerData?.latitude
        updateTripDto.pickup_longitude = customerData?.longitude
        updateTripDto.pickup_place_id = customerData?.place_id

        // Build and save customer address
        const addressParts = [
          customerData.primary_address,
          customerData.secondary_address,
          customerData.city?.name,
          customerData.state?.name,
          customerData.country?.name,
          customerData.zip_code,
        ].filter(Boolean)

        updateTripDto.pick_up_location = addressParts.join(", ")
      }

      if (updateTripDto?.dropoff_location_type === "residence") {
        const customerData: any = await this.customerRepository.getByParams({
          where: {
            id: tripData.customer_id,
          },
          findOne: true,
          select: [
            "id",
            "latitude",
            "longitude",
            "place_id",
            "primary_address",
            "secondary_address",
            "city",
            "state",
            "country",
            "zip_code",
          ],
        })

        if (isEmpty(customerData)) {
          return failureResponse(
            code.DATA_NOT_FOUND,
            errorMessage(messageKey.data_not_found, { ":data": "Customer" }),
          )
        }
        updateTripDto.dropoff_latitude = customerData?.latitude
        updateTripDto.dropoff_longitude = customerData?.longitude
        updateTripDto.dropoff_place_id = customerData?.place_id

        // Build and save customer address
        const addressParts = [
          customerData.primary_address,
          customerData.secondary_address,
          customerData.city?.name,
          customerData.state?.name,
          customerData.country?.name,
          customerData.zip_code,
        ].filter(Boolean)

        updateTripDto.drop_off_location = addressParts.join(", ")
      }

      if (updateTripDto?.pickup_location_type === "hospital") {
        const isHospitalExist: any = await this.hospitalRepository.getByParams({
          where: {
            id: updateTripDto?.pickup_hospital_id,
          },
          findOne: true,
        })

        if (isEmpty(isHospitalExist)) {
          return failureResponse(
            code.DATA_NOT_FOUND,
            errorMessage(messageKey.data_not_found, { ":data": "Trip" }),
          )
        }
        updateTripDto.pickup_latitude = isHospitalExist?.latitude
        updateTripDto.pickup_longitude = isHospitalExist?.longitude
        updateTripDto.pick_up_location = isHospitalExist?.address
        updateTripDto.pickup_place_id = isHospitalExist?.place_id
      }

      if (updateTripDto?.dropoff_location_type === "hospital") {
        const isHospitalExist: any = await this.hospitalRepository.getByParams({
          where: {
            id: updateTripDto?.dropoff_hospital_id,
          },
          findOne: true,
        })

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

        updateTripDto.dropoff_latitude = isHospitalExist?.latitude
        updateTripDto.dropoff_longitude = isHospitalExist?.longitude
        updateTripDto.drop_off_location = isHospitalExist?.address
        updateTripDto.dropoff_place_id = isHospitalExist?.place_id
      }
    }

    // Update city_id from customer's pricing_city_id when trip is draft (steps 1, 2, 3)
    const isDraft =
      tripData?.status === STATUS.ACTIVE ||
      tripData?.status === STATUS.REQUESTED_BY_CUSTOMER

    const isStep1Or2Or3 = [1, 2, 3].includes(updateTripDto?.current_step)

    if (isDraft && isStep1Or2Or3) {
      const customerId =
        updateTripDto?.current_step === 1
          ? updateTripDto?.customer_id
          : tripData?.customer_id

      if (!isEmpty(customerId)) {
        const customerForCity: any = await this.customerRepository.getByParams({
          where: { id: customerId },
          findOne: true,
          select: ["id", "pricing_city_id"],
        })
        if (!isEmpty(customerForCity?.pricing_city_id)) {
          tripData.city_id = customerForCity.pricing_city_id
        }
      }
    }

    // City validation: only when trip status is draft; city from customer's pricing_city_id
    if (
      tripData?.status === STATUS.ACTIVE ||
      tripData?.status === STATUS.REQUESTED_BY_CUSTOMER
    ) {
      const customerIdForCity =
        updateTripDto?.current_step === 1
          ? updateTripDto?.customer_id
          : tripData?.customer_id

      if (!isEmpty(customerIdForCity)) {
        const customerForCityValidation: any =
          await this.customerRepository.getByParams({
            where: { id: customerIdForCity },
            findOne: true,
            select: ["id", "pricing_city_id"],
          })

        if (!isEmpty(customerForCityValidation?.pricing_city_id)) {
          const cityId: any = await this.cityRepository.getByParams({
            where: { id: customerForCityValidation.pricing_city_id },
            findOne: true,
            select: ["id", "name"],
          })

          if (isEmpty(cityId)) {
            return failureResponse(
              code.DATA_NOT_FOUND,
              "City from customer's pricing city is not configured into the system. Please add it from the Master setting or contact Admin.",
            )
          }

          const forceUpdate =
            typeof updateTripDto?.force_update === "string"
              ? (updateTripDto?.force_update as string).toLowerCase() === "true"
              : typeof updateTripDto.force_update === "boolean"
                ? updateTripDto.force_update
                : false

          if (
            previousCityId !== cityId?.id &&
            !forceUpdate &&
            !isEmpty(previousCityId)
          ) {
            return failureResponse(
              code.CONFLICT,
              "Changing the city will remove previously added add-ons",
            )
          }
          if (
            previousCityId !== cityId?.id &&
            forceUpdate &&
            !isEmpty(previousCityId)
          ) {
            tripData.addons = []
          }
          tripData.city_id = cityId.id
        }
      }
    }

    const cleanedPayload = await this.getStepWisePayload(
      updateTripDto.current_step,
      updateTripDto,
    )

    Object.assign(tripData, cleanedPayload)

    if (updateTripDto?.current_step === 1) {
      if (isEmpty(updateTripDto?.client_id)) {
        const defaultPlan: any = await this.planRepository.getByParams({
          where: { is_default: true },
          findOne: true,
        })

        if (isEmpty(defaultPlan)) {
          return failureResponse(
            code.DATA_NOT_FOUND,
            errorMessage(messageKey.data_not_found, {
              ":data": "Default Plan",
            }),
          )
        }

        tripData.plan_id = defaultPlan.id
      } else if (updateTripDto?.client_id) {
        const now: any = moment().startOf("day").format("YYYY-MM-DD")

        const activeContract: any =
          await this.clientContractRepository.getByParams({
            where: {
              client_company_id: updateTripDto.client_id,
              status: "active",
              start_date: { lte: now },
              end_date: { gte: now },
            },
            findOne: true,
          })

        if (isEmpty(activeContract)) {
          return failureResponse(
            code.DATA_NOT_FOUND,
            errorMessage(messageKey.data_not_found, {
              ":data": "Active Contract",
            }),
          )
        }

        tripData.plan_id = activeContract.payment_plan_id
      }
    }

    if (
      updateTripDto?.current_step === 2 &&
      updateTripDto.pickup_datetime &&
      updateTripDto.dropoff_datetime
    ) {
      const validationError = await this.validateDriverAndFleetAvailability(
        id, // trip ID
        updateTripDto.pickup_datetime,
        updateTripDto.dropoff_datetime, // new trip end time
      )

      if (validationError) return validationError
    }

    if (!isEmpty(updateTripDto.addons)) {
      const tagIdArray = Array.isArray(updateTripDto.addons)
        ? updateTripDto.addons
        : String(updateTripDto.addons)
            .split(",")
            .map((id) => Number(id.trim()))

      const tags: any = await this.addOnRepository.getByParams({
        whereIn: { id: tagIdArray },
        select: ["id"],
      })

      tripData.addons = tags
    }

    if (!isEmpty(updateTripDto?.escort_ids)) {
      const escortIdsArray = Array.isArray(updateTripDto.escort_ids)
        ? updateTripDto.escort_ids
        : String(updateTripDto.escort_ids)
            .split(",")
            .map((id) => Number(id.trim()))

      const tags: any = await this.escortRepository.getByParams({
        whereIn: { id: escortIdsArray },
        select: ["id"],
      })

      tripData.escorts = tags
    } else if (updateTripDto?.current_step === 3) {
      tripData.escorts = []
    }

    const tripManagement = await this.tripRepository.save(tripData)

    // Recalculate base pricing when city or trip type changes on step 2
    if (updateTripDto?.current_step === 2) {
      const hasCityChanged =
        typeof previousCityId !== "undefined" &&
        tripManagement.city_id !== previousCityId

      const hasTripTypeChanged =
        typeof previousTripTypeId !== "undefined" &&
        tripManagement.trip_type_id !== previousTripTypeId

      if (hasCityChanged || hasTripTypeChanged) {
        // Only recalculate pricing when a fleet is assigned to this trip
        const hasFleetAssignment = await this.tripFleetAssignmentRepository
          .createQueryBuilder("tfa")
          .where("tfa.trip_id = :tripId", { tripId: tripManagement.id })
          .getOne()

        if (!isEmpty(hasFleetAssignment)) {
          // Remove existing base pricing so it can be rebuilt
          await this.tripBasePricingRepository.remove(
            { trip_id: tripManagement.id },
            undefined,
            true,
          )

          // Recalculate and persist new base pricing for this trip
          await this.calculateTripPrice(tripManagement.id)
        }
      }
    }

    if (updateTripDto.status && updateTripDto.status !== previousStatus) {
      await this.tripTimelineHistoryRepository.save({
        trip_id: tripManagement.id,
        changed_by_id: teamMemberId || null,
        status: updateTripDto.status,
        previous_status: previousStatus,
      })
    }

    if (
      updateTripDto?.current_step === 1 &&
      !isEmpty(updateTripDto?.episode_id) &&
      !isEmpty(updateTripDto?.log_id)
    ) {
      const tripLogPayload = {
        episode_id: updateTripDto?.episode_id,
        log_id: updateTripDto?.log_id,
        trip_id: id,
      }

      await this.tripLogsRepository.save(tripLogPayload, { trip_id: id })
    }
    // const { hasChanges, changes } = await this.getChangedTripFields(
    //   existingTripData,
    //   tripManagement,
    // )

    // if (hasChanges) {

    if (updateTripDto?.current_step === 3) {
      // remove data from cancelled trip
      await this.tripCancelRepository.remove(
        {
          trip_id: tripData.id,
        },
        undefined,
        true,
      )
      this.sendTripCreateNotification(tripData.id, userId)

      // Send trip confirmation notification to customer
      this.sendCustomerNotification(tripData.id, "tripConfirm", {
        PICKUP_LOCATION: tripData.pick_up_location,
        DROP_LOCATION: tripData.drop_off_location,
        PICKUP_DATE_TIME: convertUtcToTimezone(
          tripData?.pickup_datetime,
          tripData?.trip_timezone,
          "YYYY-MM-DD hh:mm A",
        ) as string,
      })
    }
    // }

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

  async validateDriverAndFleetAvailability(
    tripId: number,
    startTime: any,
    endTime: any,
  ) {
    const existingAssignment: any = await this.tripFleetRepository.getByParams({
      where: {
        trip_id: tripId,
      },
      findOne: true,
    })

    if (!existingAssignment) {
      // No assignment yet → No validation required
      return
    }

    const { fleet_id: fleetId, driver_id: driverId } = existingAssignment

    const overlappingAssignment = await this.tripFleetAssignmentRepository
      .createQueryBuilder("tfa")
      .innerJoin("tfa.trip", "t")
      .where(`(tfa.fleet_id = :fleetId OR tfa.driver_id = :driverId)`, {
        fleetId,
        driverId,
      })
      .andWhere("tfa.trip_id != :tripId", { tripId })
      .andWhere("t.status != :canceledStatus", {
        canceledStatus: STATUS.CANCELLED,
      })
      .andWhere(
        `
      t.pickup_datetime < :dropoff_datetime
      AND t.dropoff_datetime > :pickup_datetime
    `,
        {
          pickup_datetime: startTime,
          dropoff_datetime: endTime,
        },
      )
      .getOne()

    if (!isEmpty(overlappingAssignment)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        "The assigned fleet or driver is not available — another trip overlaps with this time.",
      )
    }
    return null
  }

  private async getChangedTripFields(
    oldTrip: Trip,
    newTripDto: Partial<Trip>,
  ): Promise<{
    hasChanges: boolean
    changes: { fieldName: string; oldValue: any; newValue: any }[]
  }> {
    const changes: { fieldName: string; oldValue: any; newValue: any }[] = []

    const fieldsToCompare = [
      { key: "pickup_datetime", name: "Pickup DateTime" },
      { key: "dropoff_datetime", name: "Dropoff DateTime" },
      { key: "pick_up_location", name: "Pickup Location" },
      { key: "drop_off_location", name: "Dropoff Location" },
      { key: "trip_type_id", name: "Trip Type" },
      { key: "customer_id", name: "Customer" },
      { key: "status", name: "Status" },
    ]

    for (const field of fieldsToCompare) {
      const oldValue = oldTrip[field.key]
      const newValue = newTripDto[field.key]

      // Special handling for IDs to fetch names for better comparison
      if (
        field.key === "trip_type_id" &&
        newValue !== undefined &&
        oldValue !== newValue
      ) {
        const oldTripType = (await this.tripTypeRepository.getByParams({
          where: { id: oldValue },
          findOne: true,
        })) as TripType
        const newTripType = (await this.tripTypeRepository.getByParams({
          where: { id: newValue },
          findOne: true,
        })) as TripType
        if (oldTripType?.name !== newTripType?.name) {
          changes.push({
            fieldName: field.name,
            oldValue: oldTripType?.name || "N/A",
            newValue: newTripType?.name || "N/A",
          })
        }
      } else if (
        field.key === "customer_id" &&
        newValue !== undefined &&
        oldValue !== newValue
      ) {
        const oldCustomer = (await this.customerRepository.getByParams({
          where: { id: oldValue },
          findOne: true,
        })) as Customer
        const newCustomer = (await this.customerRepository.getByParams({
          where: { id: newValue },
          findOne: true,
        })) as Customer
        if (oldCustomer?.customer_name !== newCustomer?.customer_name) {
          changes.push({
            fieldName: field.name,
            oldValue: oldCustomer?.customer_name || "N/A",
            newValue: newCustomer?.customer_name || "N/A",
          })
        }
      } else if (newValue !== undefined && oldValue !== newValue) {
        changes.push({
          fieldName: field.name,
          oldValue: oldValue,
          newValue: newValue,
        })
      }
    }

    return { hasChanges: changes.length > 0, changes }
  }

  async sendTripCreateNotification(tripId: number, userId: number) {
    const fullTripDetails = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .innerJoinAndSelect("trip.customer", "customer")
      .innerJoinAndSelect("customer.dispatcher", "dispatcher")
      .where("trip.id = :id", { id: tripId })
      .getOne()

    const userDetail: any = await this.userLoginRepository.getByParams({
      where: { user_id: userId },
      findOne: true,
      select: ["id"],
      relations: ["user:id,first_name,last_name,email,role_id.role:id,name"],
    })

    if (userDetail) {
      const users = await this.authService.getAdmins()
      const supperAdmin = await this.authService.getSuperAdmins()
      const superAdminUserIds = supperAdmin
        .filter((user) => user.id !== userId)
        .map((user) => user.id)
      let adminUserIds = users
        .filter((user) => user.id !== userId)
        .map((user) => user.id)

      let notificationTitle = "Trip Created Successfully"
      let dispatcher =
        fullTripDetails.customer.dispatcher.first_name +
        " " +
        fullTripDetails.customer.dispatcher.last_name
      if (userDetail.user?.role?.name) {
        const roleName = userDetail.user.role.name.toLowerCase()
        if (roleName === "dispatcher") {
          notificationTitle = "Trip Created by Dispatcher"
        } else if (roleName === "admin") {
          adminUserIds = superAdminUserIds
          dispatcher = userDetail?.user?.first_name
            ? userDetail.user.first_name + " " + userDetail.user.last_name
            : "Admin"
          notificationTitle = "Trip Created by Admin"
        }
      }

      if (adminUserIds.length > 0) {
        this.notificationService.sendNotification({
          user_ids: adminUserIds,
          title: notificationTitle,
          message: formatMessage("newTripCreatedByDispatcher", {
            CUSTOMER: fullTripDetails.customer.customer_name,
            TIME: convertUtcToTimezone(
              fullTripDetails?.pickup_datetime,
              fullTripDetails?.trip_timezone,
              "hh:mm A",
            ) as string,
            DATE: convertUtcToTimezone(
              fullTripDetails?.pickup_datetime,
              fullTripDetails?.trip_timezone,
              "YYYY-MM-DD",
            ) as string,
            DISPATCHER: dispatcher,
          }),
          data: {
            trip_id: tripId,
            type: "trip",
            trip_status: fullTripDetails?.status,
          },
        })
      }
    }
  }

  private async sendCustomerNotification(
    tripId: number,
    messageKey: keyof typeof customerMessages,
    params: any,
  ) {
    const tripDetails = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .leftJoinAndSelect("trip.customer", "customer")
      .leftJoinAndSelect("customer.customer_logins", "customer_logins")
      .leftJoinAndSelect("customer.dispatcher", "dispatcher")
      .leftJoinAndSelect("trip.assignments", "assignments")
      .leftJoinAndSelect("assignments.driver", "driver")
      .leftJoinAndSelect("assignments.fleet", "fleet")
      .leftJoinAndSelect("fleet.vehicleModel", "vehicleModel")
      .leftJoinAndSelect("trip.pickup_hospital", "pickupHospital")
      .leftJoinAndSelect("pickupHospital.city", "pickupCity")
      .leftJoinAndSelect("pickupHospital.state", "pickupState")
      .leftJoinAndSelect("trip.dropoff_hospital", "dropoffHospital")
      .leftJoinAndSelect("dropoffHospital.city", "dropoffCity")
      .leftJoinAndSelect("dropoffHospital.state", "dropoffState")
      .where("trip.id = :tripId", { tripId })
      .getOne()

    if (!tripDetails?.customer?.customer_logins?.length) {
      return
    }

    const customerUserId = tripDetails.customer.id

    const dispatcher = tripDetails.customer.dispatcher

    let pickupLocation = tripDetails.pick_up_location || ""
    if (
      tripDetails.pickup_location_type === "hospital" &&
      tripDetails.pickup_hospital?.address
    ) {
      pickupLocation = [
        tripDetails.pickup_hospital.address,
        tripDetails.pickup_hospital.city?.name,
        tripDetails.pickup_hospital.state?.name,
      ]
        .filter(Boolean)
        .join(", ")
    } else if (tripDetails.pickup_location_type === "residence") {
      pickupLocation = tripDetails.customer?.primary_address || ""
    }

    let dropoffLocation = tripDetails.drop_off_location || ""
    if (
      tripDetails.dropoff_location_type === "hospital" &&
      tripDetails.dropoff_hospital?.address
    ) {
      dropoffLocation = [
        tripDetails.dropoff_hospital.address,
        tripDetails.dropoff_hospital.city?.name,
        tripDetails.dropoff_hospital.state?.name,
      ]
        .filter(Boolean)
        .join(", ")
    } else if (tripDetails.dropoff_location_type === "residence") {
      dropoffLocation = tripDetails.customer?.primary_address || ""
    }

    // Use actual driver and vehicle data when available, fallback to provided params
    const notificationParams: Record<string, string> = {
      DRIVER_NAME: "DRIVER",
      VEHICLE_MODEL: "",
      VEHICLE_NUMBER: "",
      PICKUP_LOCATION: pickupLocation,
      DROP_LOCATION: dropoffLocation,
      PICKUP_DATE_TIME: convertUtcToTimezone(
        tripDetails?.pickup_datetime,
        tripDetails?.trip_timezone,
        "YYYY-MM-DD hh:mm A",
      ) as string,
      ETA: params.estimatedTime,
      DISPATCHER: `${dispatcher.first_name} ${dispatcher.last_name}`,
      ...params,
    }

    if (tripDetails.assignments?.length > 0) {
      const assignment = tripDetails.assignments[0]

      // Use actual driver name if available
      if (assignment.driver?.first_name) {
        const driverName =
          `${assignment.driver.first_name} ${assignment.driver.last_name || ""}`.trim()
        notificationParams.DRIVER_NAME = driverName
      }

      // Use actual vehicle information if available
      if (assignment.fleet?.vehicleModel?.name) {
        notificationParams.VEHICLE_MODEL = assignment.fleet.vehicleModel.name
      }

      if (assignment.fleet?.registration_number) {
        notificationParams.VEHICLE_NUMBER = assignment.fleet.registration_number
      }
    }

    let message = ""
    let title = ""

    if (messageKey == "startNavigation") {
      message = formatMessage("startNavigation", {
        PICKUP_LOCATION: notificationParams.PICKUP_LOCATION,
        DRIVER_NAME: notificationParams.DRIVER_NAME,
        ETA: notificationParams.ETA || "15",
      })
      title = "Driver is on the way to pick you up."
    }

    if (messageKey == "tripConfirm") {
      message = formatMessage("tripConfirm", {
        PICKUP_LOCATION: notificationParams.PICKUP_LOCATION,
        DROP_LOCATION: notificationParams.DROP_LOCATION,
        PICKUP_DATE_TIME: notificationParams.PICKUP_DATE_TIME,
      })

      title = "Trip Confirm"
    } else if (messageKey == "submitTripRequest") {
      message = formatMessage("submitTripRequest", {
        PICKUP_LOCATION: notificationParams.PICKUP_LOCATION,
        DROP_LOCATION: notificationParams.DROP_LOCATION,
        PICKUP_DATE_TIME: notificationParams.PICKUP_DATE_TIME,
        DISPATCHER: notificationParams.DISPATCHER,
      })

      title = "Trip Request"
    } else if (messageKey == "customersTripCancelled") {
      message = formatMessage("customersTripCancelled", {
        PICKUP_DATE_TIME: notificationParams.PICKUP_DATE_TIME,
      })
      title = "Trip Cancelled"
    }

    this.notificationService.sendCustomerNotification({
      user_ids: [customerUserId],
      title,
      message,
      data: {
        trip_id: tripId,
        type: "trip",
        status: tripDetails.status,
      },
    })
  }

  async sendNewTripRequestNotification(tripId: number) {
    const queryBuilder = this.tripEntityRepository
      .createQueryBuilder("trip")
      .innerJoinAndSelect("trip.assignments", "assignments")
      .innerJoinAndSelect("trip.customer", "customer")
      .innerJoinAndSelect(
        "FleetManagement",
        "fo",
        "fo.vehicle_type_id = assignments.fleet_type_id",
      )
      .innerJoinAndSelect(
        "TeamMember",
        "driver",
        "driver.reporting_to_id = customer.dispatcher_id and driver.id = fo.assigned_driver",
      )
      .leftJoinAndSelect(
        "VehicleMaintenance",
        "vm",
        "vm.fleet_id = assignments.fleet_id",
      )
      .leftJoinAndSelect("driver.users", "driver_users")
      .leftJoinAndSelect("fo.vehicle_type", "vehicle_type")
      .where("trip.id = :id", { id: tripId })
      .andWhere("DATE(trip.pickup_datetime) >= DATE(NOW())")
      .andWhere("DATE(trip.pickup_datetime) >= DATE(NOW())")
      .andWhere(
        "(vm.next_scheduled_maintenance IS NULL OR DATE(vm.next_scheduled_maintenance) != DATE(trip.pickup_datetime))",
      )
      .select([
        "trip.id",
        "trip.pick_up_location",
        "trip.pickup_datetime",
        "trip.dropoff_datetime",
        "trip.trip_timezone",
        "customer.id",
        "customer.customer_name",
        "driver.id",
        "driver.first_name",
        "driver.last_name",
        "driver_users.id",
        "vehicle_type.id",
        "vehicle_type.name",
      ])
      .addGroupBy("trip.id")
      .addGroupBy("driver.id")
      .addGroupBy("driver_users.id")
      .addGroupBy("vehicle_type.id")
      .addGroupBy("customer.id")

    const fullTripDetails = await queryBuilder.getRawMany()

    // Get unique drivers from the query result
    const uniqueDrivers = new Map<number, any>()
    for (const trip of fullTripDetails) {
      if (!uniqueDrivers.has(trip.driver_users_id)) {
        uniqueDrivers.set(trip.driver_users_id, trip)
      }
    }

    // Process notifications for each unique driver
    for (const trip of uniqueDrivers.values()) {
      const driverUserId = trip.driver_users_id
      const customerName = trip.customer_customer_name || "N/A"
      const pickupLocation = trip.trip_pick_up_location || "N/A"
      const vehicleName = trip.vehicle_type_name || "N/A"
      const currentTripPickupTime = trip.trip_pickup_datetime
      const currentTripDropoffTime = trip.trip_dropoff_datetime

      const overlappingTrips = await this.tripEntityRepository
        .createQueryBuilder("trip")
        .innerJoin("trip.assignments", "assignments")
        .where("trip.id != :currentTripId", { currentTripId: tripId })
        .andWhere("assignments.driver_id = :driverId", {
          driverId: trip.driver_id,
        })
        .andWhere("trip.status NOT IN (:...excludedStatuses)", {
          excludedStatuses: ["completed", "cancelled"],
        })
        .andWhere("trip.pickup_datetime >= CURRENT_TIMESTAMP")
        .andWhere(
          "(trip.pickup_datetime <= :currentTripDropoffTime AND (trip.dropoff_datetime >= :currentTripPickupTime OR trip.dropoff_datetime IS NULL))",
          {
            currentTripPickupTime,
            currentTripDropoffTime:
              currentTripDropoffTime || currentTripPickupTime,
          },
        )
        .getCount()

      // Only send notification if there are no overlapping trips
      if (overlappingTrips === 0) {
        this.notificationService.sendNotification({
          user_ids: [driverUserId],
          title: "New Trip Request",
          message: formatMessage("newTripRequest", {
            CUSTOMER: customerName,
            TIME: convertUtcToTimezone(
              currentTripPickupTime,
              trip?.trip_trip_timezone,
              "hh:mm A",
            ) as string,
            LOCATION: pickupLocation,
            VEHICLE: vehicleName,
          }),
          data: {
            trip_id: tripId,
            type: "trip",
            trip_status: STATUS.REQUESTED,
          },
        })
      }
    }
  }

  async getStepWisePayload(step: number, dto: UpdateTripDto) {
    const payload: any = {}

    if (step === 1) {
      const payload: any = {
        client_id:
          dto.client_id === 0 || isEmpty(dto.client_id) ? null : dto.client_id,
        client_booking_id:
          dto?.client_booking_id === 0 || isEmpty(dto?.client_booking_id)
            ? null
            : dto?.client_booking_id,
        client_contact_billing_id:
          dto.client_contact_billing_id === 0 ||
          isEmpty(dto.client_contact_billing_id)
            ? null
            : dto.client_contact_billing_id,
        current_step: dto.current_step,
        customer_id: dto.customer_id === 0 ? null : dto.customer_id,
        customer_phone_number: dto.customer_phone_number,
        customer_email: dto?.customer_email,
        customer_country_code: dto?.customer_country_code,
        customer_ref_no: dto?.customer_ref_no,
        is_direct_customer: dto?.is_direct_customer === "true" ? true : false,
        prn_number: dto?.prn_number,
        appointment_type: dto?.appointment_type,
        is_trip_otp_required:
          dto?.is_trip_otp_required === "true" ? true : false,
      }

      // Only assign trip_document if it's actually sent
      if (dto?.trip_document) {
        payload.trip_document = dto.trip_document
          .replace(/\\/g, "/")
          .replace(/^public\//, "")
      }

      // Only assign document_type if sent
      if (dto?.document_type) {
        payload.document_type = dto.document_type
      }

      return payload
    }

    if (step === 2) {
      Object.assign(payload, {
        trip_type_id: dto?.trip_type_id === 0 ? null : dto?.trip_type_id,
        trip_timezone: getTimezone(dto?.pickup_latitude, dto?.pickup_longitude),
        pickup_datetime: dto?.pickup_datetime,
        dropoff_datetime: dto?.dropoff_datetime,
        arrive_before: dto.arrive_before,
        pick_up_location: dto.pick_up_location,
        drop_off_location: dto.drop_off_location,
        pickup_place_id: dto?.pickup_place_id,
        dropoff_place_id: dto?.dropoff_place_id,
        pickup_location_type: dto?.pickup_location_type,
        dropoff_location_type: dto?.dropoff_location_type,
        pickup_hospital_id:
          dto?.pickup_hospital_id === 0 || isEmpty(dto?.pickup_hospital_id)
            ? null
            : dto?.pickup_hospital_id,
        dropoff_hospital_id:
          dto?.dropoff_hospital_id === 0 || isEmpty(dto?.dropoff_hospital_id)
            ? null
            : dto?.dropoff_hospital_id,
        service_details: dto?.service_details || null,
        current_step: dto?.current_step,
        flight_number: dto?.flight_number || null,
        flight_details: dto?.flight_details || null,
        pickup_location_lan: dto?.pickup_latitude || null,
        pickup_location_long: dto?.pickup_longitude || null,
        dropoff_location_long: dto?.dropoff_longitude || null,
        dropoff_location_lan: dto?.dropoff_latitude || null,
        estimated_distance: dto?.estimated_distance || null,
        estimated_time: dto?.estimated_time || null,
        last_leg_time: dto?.last_leg_time || null,
        last_leg_distance: dto?.last_leg_distance || null,
      })
    }

    if (step === 3) {
      Object.assign(payload, {
        luggage_information: dto?.luggage_information,
        is_patient_in_trip:
          typeof dto?.is_patient_in_trip === "string"
            ? (dto?.is_patient_in_trip as string).toLowerCase() === "true"
            : typeof dto.is_patient_in_trip === "boolean"
              ? dto.is_patient_in_trip
              : false,
        no_of_taxi: dto?.no_of_taxi,
        greet_customer:
          typeof dto?.greet_customer === "string"
            ? (dto?.greet_customer as string).toLowerCase() === "true"
            : typeof dto.greet_customer === "boolean"
              ? dto.greet_customer
              : false,
        greet_message: dto?.greet_message,
        // is_trip_open: dto?.is_trip_open,
        is_trip_open:
          typeof dto.is_trip_open === "string"
            ? (dto.is_trip_open as string).toLowerCase() === "true"
            : typeof dto.is_trip_open === "boolean"
              ? dto.is_trip_open
              : false,

        current_step: dto?.current_step,
        addons: dto?.addons ?? [],
        escort_ids: dto?.escort_ids ?? [],
        status: dto?.status,
        total_passenger: dto?.total_passenger || null,
        additional_notes: dto?.additional_notes,
        dispatch_note: dto?.dispatch_note,
      })
    }

    return payload
  }

  async isTripTypeExist(type: any) {
    const isTypeExist = await this.tripTypeRepository.getByParams({
      where: {
        name: type?.name,
      },
      findOne: true,
    })

    return isEmpty(isTypeExist)
  }

  async createTripType(type: any) {
    const isTypeExist = await this.isTripTypeExist(type)

    if (isTypeExist) {
      const tripType = new TripType()
      Object.assign(tripType, type)

      await this.tripTypeRepository.save(tripType)
    }
  }

  async getAllTripTypes() {
    const tripTypes = await this.tripTypeRepository.getByParams({
      select: ["id", "name", "type"],
      orderBy: { id: "DESC" },
    })
    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Trip Types" }),
      tripTypes,
    )
  }

  async assignFleetsToTrip(
    trip_id,
    assignFleetsTripDto: AssignFleetsToTripDto,
  ) {
    const isTripExist: any = await this.tripRepository.getByParams({
      where: { id: trip_id },
      relations: ["customer:id,dispatcher_id"],
      findOne: true,
    })

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

    const { fleet_type_id } = assignFleetsTripDto?.assignments?.[0]

    const incompatibleAddons = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .innerJoin("trip.addons", "addon")
      .where("trip.id = :tripId", { tripId: trip_id })
      .andWhere((qb) => {
        const subQuery = qb
          .subQuery()
          .select("1")
          .from("add_on_vehicle_models", "avm")
          .innerJoin("vehicle_models", "vm", "vm.id = avm.vehicle_model_id")
          .where("avm.add_on_id = addon.id")
          .andWhere("vm.vehicle_type_id = :vehicleTypeId")
          .getQuery()

        return `NOT EXISTS ${subQuery}`
      })
      .setParameter("vehicleTypeId", fleet_type_id)
      .select(["addon.id AS id", "addon.name AS name"])
      .distinct(true)
      .getRawMany()

    if (!isEmpty(incompatibleAddons)) {
      const addonNames = incompatibleAddons.map((a) => a.name).join(", ")

      return failureResponse(
        code.BAD_REQUEST,
        `The following add-ons are not compatible with the selected vehicle type: ${addonNames}`,
      )
    }

    for (const assignment of assignFleetsTripDto?.assignments) {
      // REMOVE
      if (assignment?.trip_fleet_id && assignment?.should_remove) {
        await this.tripFleetRepository.remove(
          { id: assignment.trip_fleet_id },
          null,
          true,
        )
        continue
      }

      if (
        assignment?.fleet_type_id &&
        isEmpty(assignment?.driver_id) &&
        isEmpty(assignment?.fleet_id)
      ) {
        const dispatcherId = isTripExist?.customer?.dispatcher_id

        // Validate trip datetime fields exist
        if (!isTripExist?.pickup_datetime || !isTripExist?.dropoff_datetime) {
          return failureResponse(
            code.BAD_REQUEST,
            "Trip pickup and dropoff datetime are required for fleet assignment",
          )
        }

        const startTime = isTripExist.pickup_datetime
        const endTime = isTripExist.dropoff_datetime

        const result = await this.fleetManagementEntityRepository
          .createQueryBuilder("fleet")
          .leftJoinAndSelect("fleet.vehicleMaintenances", "maintenance")
          .leftJoinAndSelect("fleet.assignedDriver", "driver")
          .where("fleet.assigned_dispatcher_id = :dispatcherId", {
            dispatcherId,
          })
          .andWhere("fleet.vehicle_type_id = :vehicleTypeId", {
            vehicleTypeId: assignment.fleet_type_id,
          })
          .andWhere("fleet.assigned_driver IS NOT NULL")
          .andWhere(
            new Brackets((qb) => {
              qb.where("maintenance.id IS NULL").orWhere(
                `maintenance.maintenance_date > :endTime OR maintenance.next_scheduled_maintenance < :startTime`,
                {
                  startTime: formateDate(startTime, "YYYY-MM-DD"),
                  endTime: formateDate(endTime, "YYYY-MM-DD"),
                },
              )
            }),
          )
          .getMany()

        if (isEmpty(result)) {
          return failureResponse(
            code.BAD_REQUEST,
            messageKey.no_driver_assigned_for_vehicle_type,
          )
        }
      }

      // UPDATE
      if (assignment?.trip_fleet_id) {
        const existingAssignment = (await this.tripFleetRepository.getByParams({
          where: { trip_id: trip_id },
          whereNotNull: ["driver_id"],
          findOne: true,
        })) as TripFleetAssignment

        // if (existingAssignment) {
        //   if (existingAssignment.fleet_id !== assignment.fleet_id) {
        //     const vehicleOperationalStatus =
        //       await this.vehicleStatusService.getStatusFromName(
        //         VEHICLE_STATUS.Operational,
        //       )
        //     if (vehicleOperationalStatus) {
        //       const vehicleStatusId = vehicleOperationalStatus.id
        //       await this.fleetManagementRepository.save(
        //         { vehicle_status_id: vehicleStatusId },
        //         {
        //           id: existingAssignment.fleet_id,
        //         },
        //       )
        //     }
        //   }
        // }

        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const { trip_fleet_id, should_remove, ...payload } = assignment
        const condition = { id: trip_fleet_id }
        await this.tripFleetRepository.save({ ...payload, trip_id }, condition)

        if (trip_id && !assignment.driver_id && !assignment.fleet_id) {
          await this.sendNewTripRequestNotification(trip_id)
        }

        if (
          existingAssignment &&
          existingAssignment?.driver_id &&
          existingAssignment?.driver_id !== assignment?.driver_id
        ) {
          const notificationDetails = await this.getDriverUserIdsAndTripDetails(
            trip_id,
            assignment.driver_id,
          )
          if (notificationDetails?.driverUserIds.length > 0) {
            this.notificationService.sendNotification({
              user_ids: notificationDetails.driverUserIds,
              title: "New Trip Assigned",
              message: formatMessage("newTripAssigned", {
                CUSTOMER: notificationDetails.customerName,
                TIME: convertUtcToTimezone(
                  notificationDetails?.pickupDatetime,
                  notificationDetails?.timezone,
                  "hh:mm A",
                ) as string,
                LOCATION: notificationDetails.pickupLocation,
              }),
              data: {
                trip_id: trip_id,
                type: "trip",
                trip_status: STATUS.SCHEDULED,
              },
            })
          }
        }
        continue
      }

      // ADD
      if (isEmpty(assignment?.trip_fleet_id) && !assignment?.should_remove) {
        // 1. Check if this fleet is already assigned to another driver and not released
        // const existingAssignment = await this.tripFleetRepository.getByParams({
        //   where: {
        //     fleet_id: assignment.fleet_id,
        //   },
        //   whereNotIn: {
        //     driver_id: [assignment?.driver_id],
        //   },
        //   whereNull: ["trip_completed_at"],
        //   findOne: true,
        // })

        // if (!isEmpty(existingAssignment)) {
        //   return failureResponse(
        //     code.BAD_REQUEST,
        //     messageKey.driver_already_has_vehicle,
        //   )
        // }

        let fleetResult: any
        let currentAssignedDriver: any
        if (!isEmpty(assignment?.fleet_id)) {
          // 2. Check fleet_operations (FleetManagement) for assigned_driver
          fleetResult = await this.fleetManagementService.findOne(
            assignment.fleet_id,
          )

          if (isEmpty(fleetResult) || isEmpty(fleetResult.data)) {
            return failureResponse(
              code.DATA_NOT_FOUND,
              errorMessage(messageKey.data_not_found, { ":data": "Fleet" }),
            )
          }

          currentAssignedDriver = (
            fleetResult.data as { assigned_driver?: number }
          ).assigned_driver
        }

        // If assigned_driver is set and not equal to payload driver_id, error
        if (
          !isEmpty(currentAssignedDriver) &&
          assignment.driver_id !== currentAssignedDriver
        ) {
          return failureResponse(
            code.BAD_REQUEST,
            messageKey.driver_already_has_vehicle,
          )
        }

        // If assigned_driver is empty, set it to driver_id
        // if (isEmpty(currentAssignedDriver) && !isEmpty(assignment.driver_id)) {
        //   await this.fleetManagementService.update(assignment.fleet_id, {
        //     assigned_driver: assignment.driver_id,
        //   })
        // }

        await this.tripFleetRepository.save({
          ...assignment,
          trip_id,
        })

        if (trip_id && !assignment.driver_id && !assignment.fleet_id) {
          await this.sendNewTripRequestNotification(trip_id)
        }

        const notificationDetails = await this.getDriverUserIdsAndTripDetails(
          trip_id,
          assignment.driver_id,
        )
        if (notificationDetails?.driverUserIds.length > 0) {
          this.notificationService.sendNotification({
            user_ids: notificationDetails.driverUserIds,
            title: "New Trip Assigned",
            message: formatMessage("newTripAssigned", {
              CUSTOMER: notificationDetails.customerName,
              TIME: convertUtcToTimezone(
                notificationDetails?.pickupDatetime,
                notificationDetails?.timezone,
                "hh:mm A",
              ) as string,
              LOCATION: notificationDetails.pickupLocation,
            }),
            data: {
              trip_id: trip_id,
              type: "trip",
              trip_status: STATUS.SCHEDULED,
            },
          })
          this.logger.log(
            `Sent new trip assigned notification for Trip ID: ${trip_id} to driver ${assignment.driver_id}.`,
          )
        }
      }
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, {
        ":data": "Trip Fleet Assignments",
      }),
    )
  }

  async updateTripBabySeats(updateDto: UpdateTripBabySeatDto) {
    const { trip_id, baby_seats } = updateDto

    const isTripExist = await this.tripRepository.getByParams({
      where: { id: trip_id },
      findOne: true,
    })

    if (isEmpty(isTripExist)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip",
        }),
      )
    }

    const childSeatAddon: any = await this.addOnRepository.getByParams({
      where: {
        name: addOnsEnum.CHILD_SEAT,
      },
      findOne: true,
    })

    for (const seat of baby_seats) {
      const { id, direction, should_remove } = seat

      if (id && should_remove) {
        await this.tripBabySeatRepository.remove({ id }, null, true)
      } else if (id) {
        const seatEntity = new TripBabySeat()
        Object.assign(seatEntity, {
          direction,
          trip: { id: trip_id },
          addon: { id: childSeatAddon.id },
        })
        await this.tripBabySeatRepository.save(seatEntity, { id })
      } else {
        const newSeat = new TripBabySeat()
        Object.assign(newSeat, {
          direction,
          trip: { id: trip_id },
          addon: { id: childSeatAddon.id },
        })
        await this.tripBabySeatRepository.save(newSeat)
      }
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, {
        ":data": "Trip baby seats",
      }),
    )
  }

  /**
   * Get all available drivers for a given trip.
   *
   * This API:
   *  - Finds the trip and its associated customer & dispatcher.
   *      • Report to the same dispatcher as the trip's customer.
   *      • Have status = 'active'.
   *      • Are not already assigned to another trip that overlaps with
   *        the pickup/dropoff schedule of the current trip.
   */

  async getAvailableDriversForTrip(
    tripId: number,
    paginationDto: PaginationDto,
  ) {
    const { skip, limit } = paginationDto

    // Fetch trip details along with dispatcher
    const trip: any = await this.tripRepository.getByParams({
      where: { id: tripId },
      relations: ["customer.dispatcher:id"],
      findOne: true,
    })

    const { pickup_datetime, dropoff_datetime } = trip

    const query = this.teamMemberEntityRepository
      .createQueryBuilder("driver")
      .leftJoinAndSelect("driver.languages", "languages")
      .select([
        "driver.id",
        "driver.first_name",
        "driver.last_name",
        "driver.country_code",
        "driver.phone_number",
        "languages.id",
        "languages.code",
        "languages.name",
      ])
      .andWhere("driver.reporting_to_id = :dispatcherId", {
        dispatcherId: trip?.customer?.dispatcher?.id,
      })
      .andWhere("driver.status = :status", { status: "active" })
      .andWhere((qb) => {
        const subQuery = qb
          .subQuery()
          .select("tfa.driver_id")
          .from(TripFleetAssignment, "tfa")
          .leftJoin(Trip, "t", "t.id = tfa.trip_id AND t.deleted_at IS NULL")
          .where("tfa.driver_id IS NOT NULL") // ignore null driver assignments
          .andWhere("tfa.trip_completed_at IS NULL") // only active trips
          .andWhere("t.status != 'cancelled'")
          .andWhere("tfa.trip_id != :currentTripId")
          .andWhere(
            `
          t.pickup_datetime < :dropoff_datetime
          AND t.dropoff_datetime > :pickup_datetime
        `,
          )
          .getQuery()
        return `driver.id NOT IN ${subQuery}`
      })
      .setParameters({
        pickup_datetime,
        dropoff_datetime,
        currentTripId: tripId,
      })
      .skip(skip)
      .take(limit)

    const [drivers, count] = await query.getManyAndCount()

    return successResponse(200, messageKey.data_retrieve, {
      count,
      data: drivers,
    })
  }

  async createTripCancellation(
    createTripCancellationDto: CreateTripCancellationDto,
    type: string,
  ) {
    const existingTrip = (await this.tripRepository["entity"]
      .createQueryBuilder("trip")
      .leftJoinAndSelect("trip.customer", "customer")
      .leftJoinAndSelect("customer.dispatcher", "dispatcher")
      .leftJoinAndSelect("dispatcher.users", "dispatcher_users") // Add this line
      .leftJoinAndSelect("trip.assignments", "assignments")
      .leftJoinAndSelect("assignments.driver", "driver")
      .leftJoinAndSelect("driver.users", "driver_users")
      .where("trip.id = :id", { id: createTripCancellationDto.trip_id })
      .getOne()) as Trip

    const previousStatus = existingTrip.status

    const driverUserIds =
      existingTrip.assignments?.flatMap(
        (a) => a.driver?.users?.map((u) => u.id) ?? [],
      ) ?? []

    const tripCancellationType = new TripCancellation()
    Object.assign(tripCancellationType, createTripCancellationDto)
    await this.tripCancelRepository.save(tripCancellationType)

    await this.tripRepository.save(
      {
        status: STATUS.CANCELLED,
      },
      { id: createTripCancellationDto.trip_id },
    )

    // Save timeline history
    if (type !== "customer")
      await this.tripTimelineHistoryRepository.save({
        trip_id: createTripCancellationDto.trip_id,
        status: STATUS.CANCELLED,
        previous_status: previousStatus,
        changed_by_id: createTripCancellationDto.canceled_by_id || null,
      })
    else {
      await this.tripTimelineHistoryRepository.save({
        trip_id: createTripCancellationDto.trip_id,
        status: STATUS.CANCELLED,
        previous_status: previousStatus,
        customer_id: createTripCancellationDto.canceled_by_id || null,
      })
    }

    const tripFleet = (await this.tripFleetRepository.getByParams({
      where: { trip_id: createTripCancellationDto.trip_id },
      findOne: true,
    })) as TripFleetAssignment

    const vehicleStatus = await this.vehicleStatusService.getStatusFromName(
      VEHICLE_STATUS.Operational,
    )
    if (vehicleStatus && tripFleet?.fleet_id) {
      const vehicleStatusId = vehicleStatus.id
      await this.fleetManagementRepository.save(
        { vehicle_status_id: vehicleStatusId },
        {
          id: tripFleet.fleet_id,
        },
      )
    }

    if (
      createTripCancellationDto.canceled_by !== CanceledBy.DRIVER &&
      driverUserIds.length > 0
    ) {
      this.notificationService.sendNotification({
        user_ids: driverUserIds,
        title: "Trip Cancellation",
        message: formatMessage("tripCancelled", {
          TIME: convertUtcToTimezone(
            existingTrip?.pickup_datetime,
            existingTrip?.trip_timezone,
            "hh:mm A",
          ) as string,
          CUSTOMER: existingTrip.customer.customer_name,
        }),
        data: {
          trip_id: createTripCancellationDto.trip_id,
          type: "trip",
          trip_status: STATUS.CANCELLED,
        },
      })
    }

    // this.notificationService.updateNotificationByTripId(
    //   createTripCancellationDto.trip_id,
    // )
    this.sendCustomerNotification(existingTrip.id, "customersTripCancelled", {
      PICKUP_DATE_TIME: convertUtcToTimezone(
        existingTrip?.pickup_datetime,
        existingTrip?.trip_timezone,
        "YYYY-MM-DD hh:mm A",
      ) as string,
    })

    const admins = await this.authService.getAdmins()
    const superAdmins = await this.authService.getSuperAdmins()
    const adminIds = admins.map((user) => user.id)
    const superAdminIds = superAdmins.map((user) => user.id)
    const dispatcherUserIds = existingTrip.customer?.dispatcher?.users?.map(
      (u) => u.id,
    )
    const userIds = []
    if (createTripCancellationDto.canceled_by === CanceledBy.DRIVER) {
      userIds.push(...adminIds)
      if (dispatcherUserIds?.length > 0) {
        userIds.push(...dispatcherUserIds)
      }
    } else if (
      createTripCancellationDto.canceled_by === CanceledBy.DISPATCHER
    ) {
      userIds.push(...adminIds)
    } else if (createTripCancellationDto.canceled_by === CanceledBy.CUSTOMER) {
      userIds.push(...adminIds)
      if (dispatcherUserIds?.length > 0) {
        userIds.push(...dispatcherUserIds)
      }
    } else if (createTripCancellationDto.canceled_by === CanceledBy.ADMIN) {
      userIds.push(...superAdminIds)
    }
    if (userIds.length > 0) {
      let cancelledBy = ""
      if (createTripCancellationDto.canceled_by === CanceledBy.CUSTOMER) {
        cancelledBy = existingTrip.customer.customer_name
      }
      if (createTripCancellationDto.canceled_by === CanceledBy.DISPATCHER) {
        cancelledBy = existingTrip.customer.dispatcher.first_name
      }
      if (createTripCancellationDto.canceled_by === CanceledBy.DRIVER) {
        cancelledBy = existingTrip.assignments[0].driver.first_name
      }
      if (createTripCancellationDto.canceled_by === CanceledBy.ADMIN) {
        const admin = await this.teamMemberEntityRepository.findOne({
          where: { id: createTripCancellationDto.canceled_by_id },
        })
        cancelledBy = `${admin?.first_name} ${admin?.last_name}`
        cancelledBy = cancelledBy || "Admin"
      }

      this.notificationService.sendNotification({
        user_ids: userIds,
        title: "Trip Cancellation",
        message: formatMessage("tripCanceledBy", {
          DATE: convertUtcToTimezone(
            existingTrip.pickup_datetime,
            existingTrip.trip_timezone,
            "DD-MMM-YYYY",
          ) as string,
          CUSTOMER: existingTrip.customer.customer_name,
          CANCELED_BY: cancelledBy,
        }),
        data: {
          trip_id: createTripCancellationDto.trip_id,
          type: "trip",
          trip_status: STATUS.CANCELLED,
        },
      })
    }

    return successResponse(code.SUCCESS, messageKey.cancel_successfully)
  }

  async getTripCancellation(tripId: number) {
    const result = await this.tripCancelRepository.getByParams({
      where: { trip_id: tripId },
      findOne: true,
    })

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

  async updateTripCancellation(
    canceledById: number,
    tripId: number,
    updateTripCancellationDto: UpdateTripCancellationDto,
  ) {
    const tripCancellation = (await this.tripCancelRepository.getByParams({
      where: { trip_id: tripId, canceled_by_id: canceledById },
      findOne: true,
    })) as TripCancellation
    if (!tripCancellation) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found),
      )
    }
    const updateCancellationType = new TripCancellation()
    Object.assign(updateTripCancellationDto, updateCancellationType)
    const updatedCancellation = await this.tripCancelRepository.save(
      updateCancellationType,
      { trip_id: tripId },
    )
    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, {
        ":data": "Trip cancellation",
      }),
      updatedCancellation,
    )
  }

  async getSelectedAddOnsForTrip(trip_id: number) {
    const trip: any = await this.tripRepository.getByParams({
      where: { id: trip_id },
      select: ["id", "is_direct_customer", "plan_id", "city_id"],
      relations: ["assignments"],
      findOne: true,
    })

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

    /**
     * NORMAL ADD-ONS
     * Fetch all add-ons associated with the trip (excluding baby seats)
     *
     * Pricing Priority Logic:
     * 1. Trip-specific add-on pricing (trip_addons_pricing) – highest priority
     * 2. City + Vehicle Type-specific pricing (add_ons_pricing)
     * 3. Vehicle Type-specific pricing only (city_id IS NULL)
     * 4. City-specific pricing only (vehicle_type_id IS NULL)
     * 5. Global pricing (both city_id and vehicle_type_id are NULL)
     *
     * Notes:
     * - DISTINCT ON is used to prevent duplicate add-ons
     * - COALESCE picks trip-specific price first, falls back to add_ons_pricing
     * - Deleted add-ons and deleted pricing entries are excluded
     */

    const normalAddOns = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .select([
        "addon.id AS addon_id",
        "addon.name AS addon_name",
        "COALESCE(tripAddonPricing.price, addonPricing.price) AS price",
        "tripAddonPricing.id AS trip_addon_pricing_id",
        "tripAddonPricing.quantity AS quantity",
        "tripAddonPricing.id AS trip_addon_pricing_id",
        "tripAddonPricing.total_price AS total_price",
        "addonPricing.city_id AS city_id",
        "addonPricing.vehicle_type_id AS vehicle_type_id",
        "NULL AS direction",
      ])
      .innerJoin("trip_addons", "tripAddon", "tripAddon.trip_id = trip.id")
      .innerJoin("add_ons", "addon", "addon.id = tripAddon.addon_id")
      .leftJoin(
        "add_ons_pricing",
        "addonPricing",
        `addonPricing.add_on_id = addon.id
       AND addonPricing.plan_id = :planId
       AND (addonPricing.vehicle_type_id = :vehicleTypeId OR addonPricing.vehicle_type_id IS NULL)
       AND (addonPricing.city_id = :cityId OR addonPricing.city_id IS NULL)
       AND addonPricing.deleted_at IS NULL`,
        {
          planId: trip.plan_id,
          cityId: trip.city_id,
          vehicleTypeId: trip?.assignments?.[0]?.fleet_type_id,
        },
      )
      .leftJoin(
        "trip_addons_pricing",
        "tripAddonPricing",
        `tripAddonPricing.trip_id = trip.id 
       AND tripAddonPricing.addons_id = addon.id 
       AND tripAddonPricing.deleted_at IS NULL`,
      )
      .where("trip.id = :tripId", { tripId: trip.id })
      .andWhere("trip.deleted_at IS NULL")
      .andWhere("addon.deleted_at IS NULL")
      .distinctOn(["addon.id"])
      .orderBy("addon.id", "ASC")
      .addOrderBy(
        `
      CASE
        WHEN tripAddonPricing.price IS NOT NULL THEN 0
        WHEN addonPricing.vehicle_type_id = :vehicleTypeId AND addonPricing.city_id = :cityId THEN 1
        WHEN addonPricing.vehicle_type_id = :vehicleTypeId AND addonPricing.city_id IS NULL THEN 2
        WHEN addonPricing.vehicle_type_id IS NULL AND addonPricing.city_id = :cityId THEN 3
        WHEN addonPricing.vehicle_type_id IS NULL AND addonPricing.city_id IS NULL THEN 4
        ELSE 5
      END
      `,
        "ASC",
      )
      .setParameters({
        cityId: trip.city_id,
        vehicleTypeId: trip?.assignments?.[0]?.fleet_type_id,
      })
      .getRawMany()

    /**
     * BABY SEATS
     * Fetch all baby seat add-ons for the trip
     * - First, fetch the add-on ID for CHILD_SEAT from the add_ons table
     * - Apply same pricing priority logic as normal add-ons
     * - Use trip_addons_pricing first, then fallback to add_ons_pricing
     * - Only include baby seats actually added in trip_baby_seats
     */

    const babySeatId: any = await this.addOnRepository.getByParams({
      where: { name: addOnsEnum.CHILD_SEAT },
      findOne: true,
      select: ["id"],
    })

    const babySeats = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .select([
        "addon.id AS addon_id",
        "addon.name AS addon_name",
        "COALESCE(tripAddonPricing.price, addonPricing.price) AS price",
        "COUNT(tripBabySeat.id) AS quantity",
        "tripAddonPricing.id AS trip_addon_pricing_id",
        "tripAddonPricing.total_price AS total_price",
        "addonPricing.city_id AS city_id",
        "addonPricing.vehicle_type_id AS vehicle_type_id",
        "NULL AS direction",
      ])
      .innerJoin(
        "trip_baby_seats",
        "tripBabySeat",
        "tripBabySeat.trip_id = trip.id AND tripBabySeat.addon_id = :babySeatId",
        { babySeatId: babySeatId?.id },
      )
      .innerJoin("add_ons", "addon", "addon.id = tripBabySeat.addon_id")
      .leftJoin(
        "add_ons_pricing",
        "addonPricing",
        `addonPricing.add_on_id = addon.id
     AND addonPricing.plan_id = :planId
     AND (addonPricing.vehicle_type_id = :vehicleTypeId OR addonPricing.vehicle_type_id IS NULL)
     AND (addonPricing.city_id = :cityId OR addonPricing.city_id IS NULL)
     AND addonPricing.deleted_at IS NULL`,
        {
          planId: trip.plan_id,
          cityId: trip.city_id,
          vehicleTypeId: trip?.assignments?.[0]?.fleet_type_id,
        },
      )
      .leftJoin(
        "trip_addons_pricing",
        "tripAddonPricing",
        `tripAddonPricing.trip_id = trip.id
     AND tripAddonPricing.addons_id = addon.id
     AND tripAddonPricing.deleted_at IS NULL`,
      )
      .where("trip.id = :tripId", { tripId: trip.id })
      .andWhere("trip.deleted_at IS NULL")
      .andWhere("addon.deleted_at IS NULL")
      .groupBy("addon.id")
      .addGroupBy("addon.name")
      .addGroupBy("addonPricing.price")
      .addGroupBy("addonPricing.city_id")
      .addGroupBy("addonPricing.vehicle_type_id")
      .addGroupBy("tripAddonPricing.id")
      .addGroupBy("tripAddonPricing.total_price")
      .orderBy("addon.id", "ASC")
      .addOrderBy(
        `
    CASE
      WHEN tripAddonPricing.price IS NOT NULL THEN 0
      WHEN addonPricing.vehicle_type_id = :vehicleTypeId AND addonPricing.city_id = :cityId THEN 1
      WHEN addonPricing.vehicle_type_id = :vehicleTypeId AND addonPricing.city_id IS NULL THEN 2
      WHEN addonPricing.vehicle_type_id IS NULL AND addonPricing.city_id = :cityId THEN 3
      WHEN addonPricing.vehicle_type_id IS NULL AND addonPricing.city_id IS NULL THEN 4
      ELSE 5
    END
    `,
        "ASC",
      )
      .setParameters({
        cityId: trip.city_id,
        vehicleTypeId: trip?.assignments?.[0]?.fleet_type_id,
        babySeatId: babySeatId.id,
      })
      .getRawMany()

    const allAddOns = [...normalAddOns, ...babySeats]

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "selected add-ons",
      }),
      allAddOns,
    )
  }

  async getChargeTypeForTrips(tripId: number) {
    const trip: any = await this.tripRepository.getByParams({
      where: { id: tripId },
      select: ["id", "plan_id", "city_id", "trip_type_id"],
      findOne: true,
    })

    if (isEmpty(trip)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, { ":data": "Trip" }),
      )
    }

    const query = this.tripEntityRepository
      .createQueryBuilder("trip")
      .select([
        "trip.trip_type_id as trip_type_id",
        "service_pricing.id as service_pricing_id",
        "trip_service_pricing.quantity as quantity",
        `COALESCE(trip_service_pricing.price, service_pricing.price) as price`, // ✅ prefer custom price
        "charge_type.id as charge_type_id",
        "charge_type.name as charge_type_name",
        "trip_service_pricing.id as trip_service_pricing_id", // ✅ return custom pricing id
      ])
      .innerJoin(
        "service_pricing",
        "service_pricing",
        `service_pricing.plan_id = trip.plan_id
       AND service_pricing.city_id = trip.city_id
       AND service_pricing.service_type_id = trip.trip_type_id`,
      )
      .innerJoin(
        "charge_types",
        "charge_type",
        "charge_type.id = service_pricing.charge_type_id",
      )
      // ✅ corrected left join
      .leftJoin(
        "trip_service_pricing",
        "trip_service_pricing",
        "trip_service_pricing.trip_id = trip.id AND trip_service_pricing.charge_type_id = charge_type.id",
      )
      .where("trip.id = :tripId", { tripId })

    const result = await query.getRawMany()

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "charge type" }),
      result,
    )
  }

  private async getDriverUserIdsAndTripDetails(
    tripId: number,
    driverId: number,
  ) {
    const tripDetails = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .leftJoinAndSelect("trip.customer", "customer")
      .leftJoinAndSelect("trip.assignments", "assignments")
      .leftJoinAndSelect("assignments.driver", "driver")
      .leftJoinAndSelect("driver.users", "driver_users")
      .leftJoinAndSelect("assignments.fleet", "fleet")
      .leftJoinAndSelect("fleet.vehicleModel", "vehicleModel")
      .leftJoinAndSelect("fleet.vehicle_type", "vehicle_type")
      .where("trip.id = :tripId", { tripId })
      .andWhere("driver.id = :driverId", { driverId })
      .getOne()

    if (!tripDetails) {
      return null
    }

    const driverUserIds =
      tripDetails.assignments
        ?.find((a) => a.driver?.id === driverId)
        ?.driver?.users?.map((u) => u.id)
        .filter(Boolean) ?? []

    const customerName = tripDetails.customer?.customer_name || "N/A"
    const timezone = tripDetails.trip_timezone || "America/New_York"
    const pickupDatetime = tripDetails.pickup_datetime || "N/A"
    const pickupLocation = tripDetails.pick_up_location || "N/A"
    const vehicleName =
      tripDetails.assignments?.find((a) => a.driver?.id === driverId)?.fleet
        ?.vehicleModel?.name ||
      tripDetails.assignments?.find((a) => a.driver?.id === driverId)?.fleet
        ?.vehicle_type?.name ||
      "N/A"

    return {
      driverUserIds,
      customerName,
      timezone,
      pickupDatetime,
      pickupLocation,
      vehicleName,
    }
  }

  async tripsAddOnsPricing(dto: TripAddOnsPricingDto) {
    const { trip_id, addons } = dto

    const trip = await this.tripRepository.getByParams({
      where: {
        id: trip_id,
      },
      findOne: true,
    })

    if (isEmpty(trip)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip",
        }),
      )
    }

    const existing: any = await this.tripAddOnsPricingRepository.getByParams({
      where: { trip_id },
    })

    const existingIds = existing.map((e) => e.id)

    const results = []

    for (const addon of addons) {
      const payload = {
        trip_id,
        addons_id: addon.addons_id,
        price: addon.price,
        quantity: addon.quantity,
        total_price: addon.total_price,
      }
      let saved
      if (addon.id) {
        saved = await this.tripAddOnsPricingRepository.save(payload, {
          id: addon.id,
        })
      } else {
        saved = await this.tripAddOnsPricingRepository.save(payload)
      }

      results.push(saved)
    }

    const incomingIds = addons
      .filter((addons) => addons.id)
      .map((addons) => addons.id)

    const toDelete = existingIds.filter((id) => !incomingIds.includes(id))

    for (const id of toDelete) {
      await this.tripAddOnsPricingRepository.remove({ id })
    }
    return successResponse(code.SUCCESS, messageKey.data_add, results)
  }

  async tripsServicePricing(dto: TripServicePricingDto) {
    const { trip_id, service_charge } = dto

    const trip = await this.tripRepository.getByParams({
      where: {
        id: trip_id,
      },
      findOne: true,
    })

    if (isEmpty(trip)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip",
        }),
      )
    }

    const existing: any = await this.tripServicePricingRepository.getByParams({
      where: { trip_id },
    })

    const existingIds = existing.map((e) => e.id)

    const results = []

    for (const charge of service_charge) {
      const payload = {
        trip_id,
        charge_type_id: charge.charge_type_id,
        price: charge.price,
        quantity: charge.quantity,
        total_price: charge.total_price,
      }
      let saved
      if (charge.id) {
        saved = await this.tripServicePricingRepository.save(payload, {
          id: charge.id,
        })
      } else {
        saved = await this.tripServicePricingRepository.save(payload)
      }

      results.push(saved)
    }

    const incomingIds = service_charge
      .filter((charge) => charge.id)
      .map((charge) => charge.id)

    const toDelete = existingIds.filter((id) => !incomingIds.includes(id))

    for (const id of toDelete) {
      await this.tripServicePricingRepository.remove({ id })
    }
    return successResponse(code.SUCCESS, messageKey.data_add, results)
  }

  async getTripBasePrice(tripId: number) {
    // 1️⃣ Fetch trip details
    const trip: any = await this.tripRepository.getByParams({
      where: { id: tripId },
      select: [
        "id",
        "plan_id",
        "city_id",
        "trip_type_id",
        "plan_id",
        "greet_customer",
      ],
      relations: [
        "plan:id,name,charge_type_id.charge_type:id,name,unit_of_measure",
        "assignments:id,trip_id,fleet_id,driver_id,fleet_type_id",
      ],
      findOne: true,
    })

    if (isEmpty(trip)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, { ":data": "Trip" }),
      )
    }

    // 2️⃣ Check if TripBasePricing already exists

    const existingBasePricing: any =
      await this.tripBasePricingRepository.getByParams({
        where: { trip_id: trip.id },
        select: [
          "id",
          "trip_id",
          "vehicle_type_id",
          "trip_type_id",
          "vehicle_type_price",
          "meet_greet_id",
          "meet_greet_price",
        ],
        relations: [
          "trip:id,plan_id.plan:id,name,charge_type_id.charge_type:id,name,unit_of_measure ",
        ],
        findOne: true,
      })

    // checking if the fleet type is same as vehicle type in existing base pricing
    if (
      existingBasePricing?.vehicle_type_id ===
      trip?.assignments?.[0]?.fleet_type_id
    ) {
      const response = {
        trip_id: trip?.id,
        vehicle_type_id: existingBasePricing?.vehicle_type_id || null,
        trip_type_id: existingBasePricing?.trip_type_id,
        vehicle_type_price: existingBasePricing?.vehicle_type_price || null,
        meet_greet_id: existingBasePricing?.meet_greet_id || null,
        meet_greet_price: existingBasePricing?.meet_greet_price || null,
        charge_type: existingBasePricing?.trip?.plan?.charge_type || null,
      }

      if (existingBasePricing) {
        return successResponse(
          code.SUCCESS,
          successMessage(messageKey.data_retrieve, {
            ":data": "Trip base pricing",
          }),
          response,
        )
      }
    }

    const tripFleetType: any = await this.tripFleetRepository.getByParams({
      where: {
        trip_id: trip.id,
      },
      findOne: true,
      select: ["id", "fleet_type_id"],
    })

    if (isEmpty(tripFleetType)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip Fleet",
        }),
      )
    }

    // 3️⃣ Fetch Vehicle Pricing with city fallback
    const vehiclePricing = await this.vehiclePricingEntityRepository
      .createQueryBuilder("vp")
      .where("vp.plan_id = :planId", { planId: trip.plan_id })
      .andWhere("vp.vehicle_type_id = :vehicleTypeId", {
        vehicleTypeId: tripFleetType.fleet_type_id,
      })
      .andWhere("vp.service_type_id = :serviceTypeId", {
        serviceTypeId: trip.trip_type_id,
      })
      .andWhere("(vp.city_id = :cityId OR vp.city_id IS NULL)", {
        cityId: trip.city_id,
      })
      .andWhere("vp.deleted_at IS NULL")
      .orderBy(`CASE WHEN vp.city_id IS NULL THEN 0 ELSE 1 END`, "DESC")
      .addOrderBy("vp.city_id", "DESC")
      .getOne()

    let meetGreetPricing = null
    // 4️⃣ Fetch Meet & Greet Pricing
    if (trip?.greet_customer) {
      meetGreetPricing = await this.meetAndGreetEntityRepository
        .createQueryBuilder("mgp")
        .where("mgp.plan_id = :planId", { planId: trip.plan_id })
        .andWhere("mgp.service_type_id = :serviceTypeId", {
          serviceTypeId: trip.trip_type_id,
        })
        .andWhere("mgp.deleted_at IS NULL")
        .getOne()
    }

    // 5️⃣ Prepare base pricing object
    const basePricing = {
      trip_id: trip?.id,
      vehicle_type_id: tripFleetType?.fleet_type_id || null,
      trip_type_id: trip?.trip_type_id,
      vehicle_type_price: vehiclePricing?.price || null,
      meet_greet_id: meetGreetPricing?.id || null,
      meet_greet_price: meetGreetPricing?.price || null,
      charge_type: trip?.plan?.charge_type || null,
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Trip base pricing",
      }),
      basePricing,
    )
  }

  async calculateTripPrice(tripId: number, useActualTime: boolean = false) {
    // 1. Get Base Price
    const basePriceResponse: any = await this.getTripBasePrice(tripId)

    const basePriceData = basePriceResponse?.data

    // 2. Get Selected Add-ons
    const addOnsResponse: any = await this.getSelectedAddOnsForTrip(tripId)

    const addOnsData = addOnsResponse?.data

    // 3. Get Trip Details for Estimated Time
    const trip: any = await this.tripRepository.getByParams({
      where: { id: tripId },
      select: ["id", "estimated_time", "pickup_datetime", "dropoff_datetime"],
      findOne: true,
    })

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

    // 4. Calculate Final Pricing
    let finalBasePrice = Number(basePriceData?.vehicle_type_price || 0)

    const chargeTypeUnit =
      basePriceData?.charge_type?.unit_of_measure?.toLowerCase() || ""

    let durationInMinutes = 0

    if (chargeTypeUnit === "min") {
      if (useActualTime && trip?.pickup_datetime && trip?.dropoff_datetime) {
        const start = moment(trip.pickup_datetime)
        const end = moment(trip.dropoff_datetime)
        durationInMinutes = end.diff(start, "minutes")
      } else {
        durationInMinutes = parseFloat(trip.estimated_time || "0")
      }

      const fullQuarters = Math.floor(durationInMinutes / 15)
      const pricePerQuarter = finalBasePrice / 4
      finalBasePrice = fullQuarters * pricePerQuarter
    }

    // if (chargeTypeUnit === "min") {
    //   const estimatedTime = parseFloat(trip.estimated_time || "0")
    //   // Pricing logic: Price is per hour, calculated in 15-minute quarters
    //   const fullQuarters = Math.floor(estimatedTime / 15)
    //   const pricePerQuarter = finalBasePrice / 4
    //   finalBasePrice = fullQuarters * pricePerQuarter
    // }

    // 5. Save/Update Trip Add-Ons Pricing
    // Fetch ALL existing pricing for this trip to check per add-on

    if (addOnsData.length > 0) {
      const addonPayload = addOnsData.map((addon: any) => {
        const price = Number(addon?.price) || 0
        const quantity = Number(addon?.quantity) || 1

        return {
          id: addon?.trip_addon_pricing_id || null,
          addons_id: addon?.addon_id,
          price: price.toString(),
          quantity: quantity.toString(),
          total_price: (price * quantity).toString(),
        }
      })

      const duplicateDto = new TripAddOnsPricingDto()
      duplicateDto.trip_id = tripId
      duplicateDto.addons = addonPayload

      await this.tripsAddOnsPricing(duplicateDto)
    } else {
      await this.tripAddOnsPricingRepository.remove({ trip_id: tripId })
    }

    // 6. Save/Update Trip Base Pricing
    const existingTripBasePricing: any =
      await this.tripBasePricingRepository.getByParams({
        where: {
          trip_id: tripId,
        },
        findOne: true,
      })

    if (!isEmpty(existingTripBasePricing)) {
      await this.tripBasePricingRepository.save(
        { vehicle_total_price: finalBasePrice },
        { id: existingTripBasePricing.id },
      )
    } else {
      const payload = {
        trip_id: tripId,
        vehicle_total_price: finalBasePrice,
        vehicle_type_id: basePriceData.vehicle_type_id,
        trip_type_id: basePriceData.trip_type_id,
        meet_greet_id: basePriceData.meet_greet_id,
        meet_greet_price: basePriceData.meet_greet_price,
        vehicle_type_price: basePriceData.vehicle_type_price,
      }

      await this.tripBasePricingRepository.save(payload)
    }

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

  async completePastTrips() {
    const rawDate = process.env.PAST_TRIP_CUTOFF_DATE
    const cutoffDate = moment(rawDate).add(1, "day").startOf("day").toDate()

    const trips = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .where("trip.pickup_datetime < :cutoffDate", { cutoffDate })
      .andWhere("trip.status = :scheduled", {
        scheduled: STATUS.SCHEDULED,
      })
      .getMany()

    const results = {
      total: trips.length,
      success: 0,
      failed: 0,
      errors: [],
    }

    for (const trip of trips) {
      // 1. Calculate Price

      await this.calculateTripPrice(trip?.id, true)

      // 2. Update Trip Status & Times
      const startTime = moment(trip.pickup_datetime)

      const endTime = moment(trip.dropoff_datetime)

      trip.status = STATUS.COMPLETED
      trip.trip_start_time = startTime.toDate()
      trip.trip_end_time = endTime.toDate()

      await this.tripEntityRepository.save(trip)

      const assignment: any = await this.tripFleetRepository.getByParams({
        where: { trip_id: trip.id },
        findOne: true,
      })

      if (assignment) {
        assignment.trip_completed_at = endTime.toDate() as any
        await this.tripFleetAssignmentRepository.save(assignment)
      }

      // 4. Create Timeline History
      const timeline = new TripTimelineHistory()
      timeline.trip_id = trip.id
      timeline.status = STATUS.COMPLETED
      timeline.previous_status = trip.status

      await this.tripTimelineHistoryRepository.save(timeline)

      results.success++
    }

    return successResponse(
      code.SUCCESS,
      "Bulk Trip Completion Processed",
      results,
    )
  }

  async completeTrip(tripId: number, dto: CompleteTripDto) {
    const trip: any = await this.tripRepository.getByParams({
      where: { id: tripId },
      select: ["id", "status", "plan_id"],
      relations: [
        "plan:id.charge_type:id,unit_of_measure && assignments:id,trip_id,fleet_type_id,fleet_id,driver_id.driver:id,user_id,first_name,last_name  && customer:id,customer_name,dispatcher_id",
      ],
      findOne: true,
    })

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

    const start = moment(dto.start_time)
    const end = moment(dto.end_time)

    if (!start.isValid() || !end.isValid()) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.validation_error, {
          ":data": "Trip time",
        } as any),
      )
    }

    if (end.isBefore(start)) {
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.validation_error, {
          ":data": "End time",
        }),
      )
    }

    // Mark trip completed and persist provided times
    await this.tripRepository.save(
      {
        status: STATUS.COMPLETED,
        trip_start_time: start.toDate(),
        trip_end_time: end.toDate(),
      },
      { id: tripId },
    )

    // If there is an active assignment, mark it completed too
    await this.tripFleetAssignmentRepository.update(
      { trip_id: tripId },
      { trip_completed_at: end.toDate() as any },
    )

    // Save timeline history
    await this.tripTimelineHistoryRepository.save({
      trip_id: tripId,
      status: STATUS.COMPLETED,
      previous_status: trip.status,
      changed_by_id: trip?.assignments?.[0]?.driver_id || null,
    })

    // Pricing (use payload duration when charge type is per-minute)
    const basePriceResponse: any = await this.getTripBasePrice(tripId)
    const basePriceData = basePriceResponse?.data

    const chargeTypeUnit =
      basePriceData?.charge_type?.unit_of_measure?.toLowerCase?.() || ""

    let finalBasePrice = Number(basePriceData?.vehicle_type_price || 0)

    if (chargeTypeUnit === "min") {
      const durationMinutes = Math.ceil(
        moment.duration(end.diff(start)).asMinutes(),
      )
      const fullQuarters = Math.floor(durationMinutes / 15)
      const pricePerQuarter = finalBasePrice / 4
      finalBasePrice = fullQuarters * pricePerQuarter
    }

    // Ensure add-on pricing rows exist/updated (reuses existing logic)
    const addOnsResponse: any = await this.getSelectedAddOnsForTrip(tripId)
    const addOnsData = addOnsResponse?.data || []
    if (addOnsData.length > 0) {
      const addonPayload = addOnsData.map((addon: any) => {
        const price = Number(addon?.price) || 0
        const quantity = Number(addon?.quantity) || 1

        return {
          id: addon?.trip_addon_pricing_id || null,
          addons_id: addon?.addon_id,
          price: price.toString(),
          quantity: quantity.toString(),
          total_price: (price * quantity).toString(),
        }
      })

      const duplicateDto = new TripAddOnsPricingDto()
      duplicateDto.trip_id = tripId
      duplicateDto.addons = addonPayload
      await this.tripsAddOnsPricing(duplicateDto)
    } else {
      await this.tripAddOnsPricingRepository.remove({ trip_id: tripId })
    }

    // Save/Update base pricing total
    const existingTripBasePricing: any =
      await this.tripBasePricingRepository.getByParams({
        where: { trip_id: tripId },
        findOne: true,
      })

    if (!isEmpty(existingTripBasePricing)) {
      await this.tripBasePricingRepository.save(
        { vehicle_total_price: finalBasePrice },
        { id: existingTripBasePricing.id },
      )
    } else {
      await this.tripBasePricingRepository.save({
        trip_id: tripId,
        vehicle_total_price: finalBasePrice,
        vehicle_type_id: basePriceData?.vehicle_type_id || null,
        trip_type_id: basePriceData?.trip_type_id || null,
        meet_greet_id: basePriceData?.meet_greet_id || null,
        meet_greet_price: basePriceData?.meet_greet_price || null,
        vehicle_type_price: basePriceData?.vehicle_type_price || null,
      })
    }

    const dispatcherId = trip?.customer?.dispatcher_id
    if (dispatcherId) {
      this.notificationService.sendNotification({
        user_ids: [dispatcherId],
        title: "Trip completed by driver",
        message: formatMessage("driverCompletedTrip", {
          TRIP_ID: tripId?.toString(),
          CUSTOMER: trip?.customer?.customer_name,
          DRIVER: trip?.driver?.first_name,
        }),
        data: {
          type: "trip",
          trip_id: tripId,
        },
      })
    }

    // this.sendCustomerNotification(tripId, "tripEnd", [trip.customer?.id])

    this.sendTripNotificationToCustomer(
      tripId,
      "tripEnd",
      [trip.customer?.id],
      undefined,
      STATUS.COMPLETED,
      trip.assignments?.[0]?.driver_id,
      trip.assignments?.[0]?.driver?.first_name +
        " " +
        trip.assignments?.[0]?.driver?.last_name,
    )

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

  async calculateTripPricing() {
    const trips = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .leftJoin("trip.base_pricing", "base")
      .where("base.id IS NULL")
      .andWhere("trip.deleted_at IS NULL")
      .andWhere("trip.status IN (:...statuses)", {
        statuses: [STATUS.SCHEDULED, STATUS.COMPLETED],
      })
      .select(["trip.id"])
      .getMany()

    if (!trips.length) {
      return successResponse(code.SUCCESS, "No unpriced trips found")
    }

    // 2. Loop & calculate pricing
    for (const trip of trips) {
      await this.calculateTripPrice(trip?.id, false)
    }

    return successResponse(
      code.SUCCESS,
      `Pricing calculated for ${trips.length} trips`,
    )
  }

  async updateTripBasePricing(tripPricingDto: TripPricingDto) {
    const isTripExist = await this.tripRepository.getByParams({
      where: {
        id: tripPricingDto.trip_id,
      },
      findOne: true,
    })

    if (isEmpty(isTripExist)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip",
        }),
      )
    }

    const isTripPricingExist = await this.tripBasePricingRepository.getByParams(
      {
        where: {
          trip_id: tripPricingDto.trip_id,
        },
        findOne: true,
      },
    )

    if (isEmpty(isTripPricingExist)) {
      const tripBasePricing = new TripBasePricing()

      Object.assign(tripBasePricing, tripPricingDto)

      await this.tripBasePricingRepository.save(tripBasePricing)
    } else {
      const tripBasePricing = new TripBasePricing()

      Object.assign(tripBasePricing, tripPricingDto)

      await this.tripBasePricingRepository.save(tripBasePricing, {
        trip_id: tripPricingDto.trip_id,
      })
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, { ":data": "Trip Pricing" }),
    )
  }
  async saveTripLocation(createTripTrackingDto: CreateTripTrackingDto) {
    const isTripExist = await this.tripRepository.getByParams({
      where: {
        id: createTripTrackingDto?.trip_id,
      },
      findOne: true,
    })

    if (isEmpty(isTripExist)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip",
        }),
      )
    }

    const tripTracking = new TripTracking()

    Object.assign(tripTracking, createTripTrackingDto)

    const location = await this.tripTrackingRepository.save(tripTracking)

    return location
  }

  async getLatestTripLocation(tripId: any) {
    return this.tripTrackingRepository.getByParams({
      where: {
        trip_id: tripId,
      },
      orderBy: {
        created_at: "DESC",
      },
      findOne: true,
    })
  }

  async updateTripStatusToPickup(
    trip_id: any,
    token: any,
    estimated_time?: string,
  ) {
    const isTripExist = (await this.tripRepository.getByParams({
      where: {
        id: trip_id,
      },
      relations: ["customer", "assignments.driver", "assignments.fleet"],
      findOne: true,
    })) as Trip

    if (isEmpty(isTripExist)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip",
        }),
      )
    }

    const pickupDate = formateDate(isTripExist.pickup_datetime, "YYYY-MM-DD")
    const currentDate = formateDate(new Date(), "YYYY-MM-DD")

    if (pickupDate < currentDate) {
      return failureResponse(
        code.VALIDATION,
        "Trip pickup time has already passed. Cannot start the trip.",
      )
    }

    const previousStatus = isTripExist.status

    const driverDetails: any = await this.authService.getUserByToken(token)

    const driverCurrentFleet: any =
      await this.fleetManagementRepository.getByParams({
        where: {
          assigned_driver: driverDetails?.user?.team_member_id,
          id: isTripExist.assignments?.[0]?.fleet_id,
        },
        findOne: true,
      })

    if (isEmpty(driverCurrentFleet)) {
      return failureResponse(
        code.VALIDATION,
        `Vehicle Mismatch : Switch to ${isTripExist?.assignments?.[0]?.fleet?.registration_number} to start this trip`,
      )
    }

    const onGoingTrip = await this.tripFleetAssignmentRepository
      .createQueryBuilder("tfa")
      .select("tfa.trip_id")
      .innerJoin("tfa.trip", "t")
      .where("tfa.driver_id = :driverId", {
        driverId: driverDetails?.user?.team_member_id,
      })
      .andWhere("t.status IN (:...statuses)", {
        statuses: [STATUS.ONGOING, STATUS.PICKUP],
      })
      .andWhere("tfa.released_at IS NULL")
      .andWhere("tfa.trip_completed_at IS NULL")
      .andWhere("t.deleted_at IS NULL")
      .limit(1)
      .getRawOne()

    if (onGoingTrip) {
      return failureResponse(code.VALIDATION, messageKey.active_trip_found)
    }

    const isDriverOffDuty = await this.teamMemberRepository.getByParams({
      where: {
        id: driverDetails?.user?.team_member_id,
        duty_status: dutyStatus.OFF_DUTY,
      },
      findOne: true,
    })

    if (isDriverOffDuty) {
      return failureResponse(
        code.VALIDATION,
        "Driver is currently off duty. Cannot start the trip.",
      )
    }

    await this.tripRepository.save({ status: STATUS.PICKUP }, { id: trip_id })

    // Save timeline history
    await this.tripTimelineHistoryRepository.save({
      trip_id: trip_id,
      status: STATUS.PICKUP,
      previous_status: previousStatus,
      changed_by_id: driverDetails?.user?.team_member_id || null,
    })

    // ✅ Send notification to CUSTOMER only
    if (isTripExist.customer?.id) {
      const driver = isTripExist.assignments?.[0]?.driver
      const driverName = driver
        ? `${driver.first_name} ${driver.last_name}`
        : "Driver"

      this.notificationService.sendCustomerNotification({
        user_ids: [isTripExist.customer.id],
        title: "Driver started navigation",
        message: `Trip "#T${trip_id}": ${driverName} is on the way to the pickup location.`,
        data: {
          trip_id: trip_id,
          type: "trip",
          status: STATUS.PICKUP,
          estimatedTime: estimated_time,
        },
      })
    }

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

  async createTripRequestByCustomer(
    customerTripDto: CreateTripRequestByCustomerDto,
    tripDocument?: Express.Multer.File,
  ) {
    const customerData = (await this.customerRepository.getByParams({
      where: {
        id: customerTripDto?.customer_id,
      },
      findOne: true,
      select: [
        "id",
        "customer_name",
        "is_medical_tourist",
        "client_company_id",
        "email",
        "country_code",
        "phone_number",
        "prn_number",
        "pricing_city_id",
      ],
      relations: ["dispatcher.users:id"],
    })) as Customer

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

    const pointToPointTripType: any = await this.tripTypeRepository.getByParams(
      {
        where: { name: "Point to Point" },
        findOne: true,
      },
    )

    if (!pointToPointTripType) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip Type 'Point to Point'",
        }),
      )
    }

    // Determine plan based on customer's client association
    let planId: number | null = null

    if (isEmpty(customerData.client_company_id)) {
      // Get default plan for direct customers
      const defaultPlan: any = await this.planRepository.getByParams({
        where: { is_default: true },
        findOne: true,
      })

      if (!isEmpty(defaultPlan)) {
        planId = defaultPlan.id
      }
      // If no default plan found, planId remains null
    } else {
      // Get active contract plan for client customers
      const now = moment().toDate()
      const activeContract: any =
        await this.clientContractRepository.getByParams({
          where: {
            client_company_id: customerData.client_company_id,
            status: "active",
            start_date: { lte: now },
            end_date: { gte: now },
          },
          findOne: true,
        })

      if (!isEmpty(activeContract)) {
        planId = activeContract.payment_plan_id
      }
    }

    // Create trip entity
    const trip = new Trip()
    Object.assign(trip, {
      city_id: customerData?.pricing_city_id,
      customer_id: customerTripDto.customer_id,
      pickup_datetime: customerTripDto.pickup_datetime,
      trip_timezone: getTimezone(
        customerTripDto?.pickup_location_lan,
        customerTripDto?.pickup_location_long,
      ),
      pick_up_location: customerTripDto.pick_up_location,
      drop_off_location: customerTripDto.drop_off_location,
      pickup_place_id: customerTripDto.pickup_place_id,
      dropoff_place_id: customerTripDto.dropoff_place_id,
      luggage_information: customerTripDto.luggage_information,
      pickup_location_lan: customerTripDto.pickup_location_lan,
      pickup_location_long: customerTripDto.pickup_location_long,
      dropoff_location_lan: customerTripDto.dropoff_location_lan,
      dropoff_location_long: customerTripDto.dropoff_location_long,
      client_id: customerData.client_company_id || null,
      customer_phone_number: customerData.phone_number || null,
      customer_email: customerData.email || null,
      customer_country_code: customerData.country_code || null,
      is_direct_customer: isEmpty(customerData.client_company_id)
        ? true
        : false,
      prn_number: customerData.prn_number || null,
      additional_notes: customerTripDto.additional_notes || null,
      dispatch_note: customerTripDto.dispatch_note || null,
      // Default values
      status: STATUS.REQUESTED_BY_CUSTOMER,
      pickup_location_type: "custom",
      dropoff_location_type: "custom",
      trip_type_id: pointToPointTripType.id,
      current_step: 1,
      plan_id: planId, // Will be null if no plan found
    })

    if (tripDocument) {
      const relativePath = tripDocument.path
        .replace(/\\/g, "/")
        .replace(/^public\//, "")
      trip.trip_document = relativePath
      trip.document_type = tripDocument.mimetype?.split("/")[0]
    }

    const savedTrip = await this.tripRepository.save(trip)

    const timezone = getTimezone(
      customerTripDto?.pickup_location_lan,
      customerTripDto?.pickup_location_long,
    )

    await this.tripTimelineHistoryRepository.save({
      trip_id: savedTrip.id,
      status: STATUS.REQUESTED_BY_CUSTOMER,
      // previous_status: previousStatus,
      customer_id: customerData.id,
    })

    // Send trip request notification to customer
    this.sendCustomerNotification(savedTrip.id, "submitTripRequest", {
      PICKUP_LOCATION: trip.pick_up_location,
      DROP_LOCATION: trip.drop_off_location,
      PICKUP_DATE_TIME: convertUtcToTimezone(
        trip?.pickup_datetime,
        timezone,
        "YYYY-MM-DD hh:mm A",
      ) as string,
      DISPATCHER: "Our team",
    })

    //Send notification to dispatcher & admin for approval
    const dispatcherUserId = customerData.dispatcher?.users?.[0]?.id
    if (!dispatcherUserId) {
      console.log("dispatcherUserId is null or undefined")
    }

    const userIds = dispatcherUserId ? [dispatcherUserId] : []
    const admins = await this.authService.getAdmins()
    const adminIds = admins.map((a) => a.id)
    userIds.push(...adminIds)

    if (userIds.length > 0) {
      this.notificationService.sendNotification({
        user_ids: userIds,
        title: "Customer requests a trip",
        message: formatMessage("customerRequestTrip", {
          CUSTOMER: customerData.customer_name,
          PICKUP_LOCATION: customerTripDto.pick_up_location,
          DROP_LOCATION: customerTripDto.drop_off_location,
          PICKUP_DATE_TIME: convertUtcToTimezone(
            customerTripDto?.pickup_datetime,
            timezone,
            "YYYY-MM-DD hh:mm A",
          ) as string,
        }),
        data: {
          trip_id: savedTrip.id,
          type: "trip",
          trip_status: STATUS.REQUESTED_BY_CUSTOMER,
        },
      })
    }

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

  async getTripTimeZone() {
    return successResponse(code.SUCCESS, messageKey.data_retrieve, tripTimeZone)
  }

  async getCancelledTripsCount(
    driverId: number,
    start_timestamp?: string,
    end_timestamp?: string,
  ) {
    let assignedTripsQuery = this.tripFleetAssignmentRepository
      .createQueryBuilder("tfa")
      .select("trip.id")
      .innerJoin("tfa.trip", "trip")
      .where("tfa.driver_id = :driverId", { driverId })
      .andWhere("tfa.trip_id IS NOT NULL")
      .andWhere("trip.deleted_at IS NULL")

    if (start_timestamp && end_timestamp) {
      assignedTripsQuery = assignedTripsQuery.andWhere(
        "DATE(trip.pickup_datetime) BETWEEN DATE(:start) AND DATE(:end)",
        {
          start: start_timestamp,
          end: end_timestamp,
        },
      )
    }

    const assignedTrips = await assignedTripsQuery
      .select("COUNT(DISTINCT trip.id)", "assignedTrips")
      .getRawOne()

    let query = this.tripCancelRepository["entity"]
      .createQueryBuilder("cancellation")
      .select("trip.id")
      .innerJoin(
        TripFleetAssignment,
        "assignment",
        "assignment.trip_id = cancellation.trip_id",
      )
      .innerJoin("assignment.trip", "trip")
      .where("assignment.driver_id = :driverId", { driverId })
      .andWhere("trip.deleted_at IS NULL")

    if (start_timestamp && end_timestamp) {
      query = query.andWhere(
        "DATE(trip.pickup_datetime) BETWEEN DATE(:start) AND DATE(:end)",
        {
          start: start_timestamp,
          end: end_timestamp,
        },
      )
    }

    const cancelledTrips = await query
      .select("COUNT(DISTINCT trip.id)", "cancelledTrips")
      .getRawOne()

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Cancelled trips count",
      }),
      {
        cancelledTrips: Number(cancelledTrips?.cancelledTrips || 0),
        assignedTrips: Number(assignedTrips?.assignedTrips || 0),
      },
    )
  }

  async getRating(
    driverId: number,
    start_timestamp?: string,
    end_timestamp?: string,
  ) {
    let ratingQuery = this.ratingRepository["entity"]
      .createQueryBuilder("rating")
      .innerJoin("rating.trip", "trip")
      .where("rating.rated_id = :driverId", { driverId })
      .andWhere("rating.rating_type = :ratingType", {
        ratingType: RATING_TYPES.CUSTOMER_TO_DRIVER,
      })

    if (start_timestamp && end_timestamp) {
      ratingQuery = ratingQuery.andWhere(
        "DATE(trip.pickup_datetime) BETWEEN DATE(:start) AND DATE(:end)",
        {
          start: start_timestamp,
          end: end_timestamp,
        },
      )
    }

    const ratingsWithTags = await ratingQuery.getMany()

    const averageRating =
      ratingsWithTags.length > 0
        ? parseFloat(
            (
              ratingsWithTags.reduce((sum, r) => sum + r.rating, 0) /
              ratingsWithTags.length
            ).toFixed(1),
          )
        : 0

    // Get rating count
    const ratingCount = ratingsWithTags.length

    // Calculate top tags
    const tagCount: Record<string, number> = {}
    ratingsWithTags.forEach((r) => {
      if (Array.isArray(r.tags)) {
        r.tags.forEach((tag) => {
          tagCount[tag] = (tagCount[tag] || 0) + 1
        })
      }
    })

    const topTags = Object.entries(tagCount)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 3)
      .map(([tag]) => tag)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Rating",
      }),
      { ratingCount, averageRating, topTags },
    )
  }

  async getTripStatusHistory(tripId: number) {
    const history = await this.tripTimelineHistoryRepository.getByParams({
      where: { trip_id: tripId },
      orderBy: { created_at: "ASC" },
      relations: [
        "changed_by:id,first_name,last_name && customer:id,customer_name",
      ],
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Trip status history",
      }),
      history,
    )
  }

  async updateTripType(id: any, dto: UpdateTripTypeDto) {
    const tripType: any = await this.tripTypeRepository.getByParams({
      where: { id },
      findOne: true,
    })

    if (isEmpty(tripType)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, {
          ":data": "Trip Type",
        }),
      )
    }

    // Prevent duplicate type
    const exists = await this.tripTypeRepository.getByParams({
      where: { type: dto.type, name: dto.name },
      whereNotIn: { id: [id] },
      findOne: true,
    })

    if (!isEmpty(exists)) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.already_exist, {
          ":data": "add-Trip Type",
        }),
      )
    }

    Object.assign(tripType, dto)
    return this.tripTypeRepository.save(tripType)
  }

  async fixMissingTripLocations() {
    console.log("SCRIPT START")

    const trips = (await this.tripRepository.getByParams({
      whereIn: {
        pickup_location_type: ["hospital", "residence"],
        dropoff_location_type: ["hospital", "residence"],
      },
      relations: [
        "customer",
        "customer.city",
        "customer.state",
        "customer.country",
      ],
    })) as any[]

    console.log("Total trips:", trips.length)

    for (const trip of trips) {
      let updated = false

      const pickupMissing =
        !trip.pick_up_location || trip.pick_up_location === ""
      const dropMissing =
        !trip.drop_off_location || trip.drop_off_location === ""

      if (!pickupMissing && !dropMissing) continue

      // ========= PICKUP =========
      if (pickupMissing && trip.pickup_location_type === "residence") {
        const customer = trip.customer
        if (customer) {
          const addressParts = [
            customer.primary_address,
            customer.secondary_address,
            customer.city?.name,
            customer.state?.name,
            customer.country?.name,
            customer.zip_code,
          ].filter(Boolean)

          trip.pick_up_location = addressParts.join(", ")
          trip.pickup_latitude = customer.latitude
          trip.pickup_longitude = customer.longitude
          updated = true
        }
      }

      if (pickupMissing && trip.pickup_location_type === "hospital") {
        const hospital: any = await this.hospitalRepository.getByParams({
          where: { id: trip.pickup_hospital_id },
          findOne: true,
        })

        if (hospital) {
          trip.pick_up_location = hospital.address
          trip.pickup_latitude = hospital.latitude
          trip.pickup_longitude = hospital.longitude
          updated = true
        }
      }

      // ========= DROPOFF =========
      if (dropMissing && trip.dropoff_location_type === "residence") {
        const customer = trip.customer
        if (customer) {
          const addressParts = [
            customer.primary_address,
            customer.secondary_address,
            customer.city?.name,
            customer.state?.name,
            customer.country?.name,
            customer.zip_code,
          ].filter(Boolean)

          trip.drop_off_location = addressParts.join(", ")
          trip.dropoff_latitude = customer.latitude
          trip.dropoff_longitude = customer.longitude
          updated = true
        }
      }

      if (dropMissing && trip.dropoff_location_type === "hospital") {
        const hospital: any = await this.hospitalRepository.getByParams({
          where: { id: trip.dropoff_hospital_id },
          findOne: true,
        })

        if (hospital) {
          trip.drop_off_location = hospital.address
          trip.dropoff_latitude = hospital.latitude
          trip.dropoff_longitude = hospital.longitude
          updated = true
        }
      }

      if (updated) {
        await this.tripRepository.save(trip)
        console.log("Updated trip:", trip.id)
      }
    }

    console.log("SCRIPT END")
  }

  // async findFrequentlyUsedDropoffLocations(
  //   customerId: number,
  //   currentLat?: number,
  //   currentLong?: number,
  // ) {
  //   const customer: any = await this.customerRepository.getByParams({
  //     where: { id: customerId },
  //     findOne: true,
  //   })

  //   if (isEmpty(customer)) {
  //     return failureResponse(
  //       code.BAD_REQUEST,
  //       errorMessage(messageKey.data_not_found, {
  //         ":data": "Customer",
  //       }),
  //     )
  //   }

  //   const REQUIRED_COUNT = 5

  //   // Query for frequent drop-off locations
  //   let locations = await this.tripEntityRepository
  //     .createQueryBuilder("trip")
  //     .select([
  //       "trip.drop_off_location AS drop_off_location",
  //       "trip.dropoff_location_lan AS dropoff_location_lan",
  //       "trip.dropoff_location_long AS dropoff_location_long",
  //       "COUNT(trip.id) AS visit_count",
  //       "MAX(trip.created_at) AS last_used",
  //     ])
  //     .where("trip.customer_id = :customerId", { customerId })
  //     .andWhere("trip.status = :status", { status: "completed" })
  //     .andWhere("trip.dropoff_location_lan IS NOT NULL")
  //     .andWhere("trip.dropoff_location_long IS NOT NULL")
  //     .andWhere(
  //       "(trip.dropoff_location_lan != trip.pickup_location_lan OR trip.dropoff_location_long != trip.pickup_location_long)",
  //     )
  //     .groupBy("trip.dropoff_location_lan")
  //     .addGroupBy("trip.dropoff_location_long")
  //     .addGroupBy("trip.drop_off_location")
  //     .orderBy("visit_count", "DESC")
  //     .addOrderBy("last_used", "DESC")
  //     .limit(15)
  //     .getRawMany()

  //   if (!locations || locations.length === 0) {
  //     locations = await this.tripEntityRepository
  //       .createQueryBuilder("trip")
  //       .select([
  //         "trip.drop_off_location AS drop_off_location",
  //         "trip.dropoff_location_lan AS dropoff_location_lan",
  //         "trip.dropoff_location_long AS dropoff_location_long",
  //       ])
  //       .where("trip.customer_id = :customerId", { customerId })
  //       .andWhere("trip.status = :status", { status: "completed" })
  //       .andWhere("trip.dropoff_location_lan IS NOT NULL")
  //       .andWhere("trip.dropoff_location_long IS NOT NULL")
  //       .andWhere(
  //         "(trip.dropoff_location_lan != trip.pickup_location_lan OR trip.dropoff_location_long != trip.pickup_location_long)",
  //       )
  //       .orderBy("trip.created_at", "DESC")
  //       .limit(15)
  //       .getRawMany()

  //     // Add default visit_count = 1 for last locations
  //     locations = locations.map((loc) => ({
  //       ...loc,
  //       visit_count: 1,
  //     }))
  //   }

  //   if (currentLat && currentLong) {
  //     locations = locations.filter((loc) => {
  //       const lat = parseFloat(loc.dropoff_location_lan)
  //       const long = parseFloat(loc.dropoff_location_long)
  //       // Remove if both coordinates match
  //       return !(lat === currentLat && long === currentLong)
  //     })
  //   }

  //   // Remove pickup location fields from response
  //   // locations = locations.map(
  //   //   ({ pickup_location_lan, pickup_location_long, ...rest }) => rest,
  //   // )

  //   //Take only required count
  //   locations = locations.slice(0, REQUIRED_COUNT)

  //   return successResponse(
  //     code.SUCCESS,
  //     successMessage(messageKey.data_retrieve, {
  //       ":data": "Frequently Used Dropoff Locations",
  //     }),
  //     locations,
  //   )
  // }

  async findFrequentlyUsedDropoffLocations(
    customerId: number,
    currentPlaceId?: string,
  ) {
    const customer: any = await this.customerRepository.getByParams({
      where: { id: customerId },
      findOne: true,
    })

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

    const REQUIRED_COUNT = 5

    // Query for frequent drop-off locations grouped by place_id
    let locations = await this.tripEntityRepository
      .createQueryBuilder("trip")
      .select([
        "trip.drop_off_location AS drop_off_location",
        "trip.dropoff_location_lan AS dropoff_location_lan",
        "trip.dropoff_location_long AS dropoff_location_long",
        "trip.dropoff_place_id AS dropoff_place_id",
        "COUNT(trip.id) AS visit_count",
        "MAX(trip.trip_end_time) AS last_used", // ✅ using trip_end_time
      ])
      .where("trip.customer_id = :customerId", { customerId })
      .andWhere("trip.status = :status", { status: "completed" })
      .andWhere("trip.dropoff_place_id IS NOT NULL")
      .andWhere("trip.dropoff_place_id != trip.pickup_place_id")
      .andWhere("trip.trip_end_time IS NOT NULL")
      .groupBy("trip.dropoff_place_id")
      .addGroupBy("trip.drop_off_location")
      .addGroupBy("trip.dropoff_location_lan")
      .addGroupBy("trip.dropoff_location_long")
      .orderBy("MAX(trip.trip_end_time)", "DESC")
      .addOrderBy("COUNT(trip.id)", "DESC")
      .limit(15)
      .getRawMany()

    if (!locations || locations.length === 0) {
      locations = await this.tripEntityRepository
        .createQueryBuilder("trip")
        .select([
          "trip.drop_off_location AS drop_off_location",
          "trip.dropoff_location_lan AS dropoff_location_lan",
          "trip.dropoff_location_long AS dropoff_location_long",
          "trip.dropoff_place_id AS dropoff_place_id",
          "trip.trip_end_time AS last_used",
        ])
        .where("trip.customer_id = :customerId", { customerId })
        .andWhere("trip.status = :status", { status: "completed" })
        .andWhere("trip.dropoff_place_id IS NOT NULL")
        .andWhere("trip.dropoff_place_id != trip.pickup_place_id")
        .andWhere("trip.trip_end_time IS NOT NULL")
        .orderBy("trip.trip_end_time", "DESC")
        .limit(15)
        .getRawMany()

      // Deduplicate by place_id (keep most recent), add default visit_count
      const seen = new Set<string>()
      locations = locations
        .filter((loc) => {
          if (seen.has(loc.dropoff_place_id)) return false
          seen.add(loc.dropoff_place_id)
          return true
        })
        .map((loc) => ({ ...loc, visit_count: 1 }))
    }

    // Filter out current location by place_id
    if (currentPlaceId) {
      locations = locations.filter(
        (loc) => loc.dropoff_place_id !== currentPlaceId,
      )
    }

    locations = locations.slice(0, REQUIRED_COUNT)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Frequently Used Dropoff Locations",
      }),
      locations,
    )
  }

  async createRoundTrip(tripId: number) {
    const trip: any = await this.tripRepository.getByParams({
      where: { id: tripId },
      findOne: true,
    })

    if (isEmpty(trip)) {
      return failureResponse(
        code.BAD_REQUEST,
        errorMessage(messageKey.data_not_found, { ":data": "Trip" }),
      )
    }
    if (isEmpty(trip.client_id)) {
      const defaultPlan: any = await this.planRepository.getByParams({
        where: { is_default: true },
        findOne: true,
      })

      if (isEmpty(defaultPlan)) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, { ":data": "Default Plan" }),
        )
      }

      trip.plan_id = defaultPlan.id
    } else if (trip?.client_id) {
      const now = moment().toDate()

      const activeContract: any =
        await this.clientContractRepository.getByParams({
          where: {
            client_company_id: trip.client_id,
            status: "active",
            start_date: { lte: now },
            end_date: { gte: now },
          },
          findOne: true,
        })

      if (isEmpty(activeContract)) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, {
            ":data": "Active Contract",
          }),
        )
      }

      trip.plan_id = activeContract.payment_plan_id
    }

    const location = await getCityFromLatLngBackend(
      trip?.dropoff_location_lan,
      trip?.dropoff_location_long,
    )

    const country: any = await this.countryRepository.getByParams({
      whereLower: { name: location?.country },
      findOne: true,
      select: ["id"],
    })

    const state: any = await this.stateRepository.getByParams({
      whereLower: { name: location?.state },
      where: { country_id: country?.id },
      findOne: true,
      select: ["id"],
    })

    const city: any = await this.cityRepository.getByParams({
      whereLower: { name: location?.city },
      where: { state_id: state?.id },
      findOne: true,
      select: ["id"],
    })

    trip.city_id = city?.id ?? null

    const returnTripPayload: Partial<Trip> = {
      client_id: trip.client_id ?? null,
      client_booking_id: trip.client_booking_id ?? null,
      client_contact_billing_id: trip.client_contact_billing_id ?? null,
      customer_id: trip.customer_id ?? null,
      customer_phone_number: trip.customer_phone_number,
      customer_email: trip.customer_email,
      customer_country_code: trip.customer_country_code,
      customer_ref_no: trip.customer_ref_no,
      is_direct_customer: trip.is_direct_customer,
      prn_number: trip.prn_number,
      appointment_type: trip.appointment_type,
      plan_id: trip.plan_id ?? null,
      city_id: trip.city_id ?? null,

      trip_type_id: trip.trip_type_id ?? null,
      trip_timezone: trip.trip_timezone,
      current_step: 1,

      pick_up_location: trip.drop_off_location,
      drop_off_location: trip.pick_up_location,

      pickup_location_type: trip.dropoff_location_type,
      dropoff_location_type: trip.pickup_location_type,

      pickup_hospital_id: trip.dropoff_hospital_id ?? null,
      dropoff_hospital_id: trip.pickup_hospital_id ?? null,

      // ---- Flight & service ----
      service_details: trip.service_details,

      // ---- Location coordinates (swapped) ----
      pickup_location_lan: trip.dropoff_location_lan ?? null,
      pickup_location_long: trip.dropoff_location_long ?? null,
      dropoff_location_lan: trip.pickup_location_lan ?? null,
      dropoff_location_long: trip.pickup_location_long ?? null,

      // ---- Estimation ----
      estimated_distance: null,
      estimated_time: null,
      last_leg_time: null,
      last_leg_distance: null,
    }

    await this.tripRepository.save(returnTripPayload)

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

  async changeStatusToDraft(tripId: number) {
    const trip: any = await this.tripRepository.getByParams({
      where: { id: tripId },
      select: ["id", "status"],
      findOne: true,
    })

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

    await this.tripRepository.save({ status: STATUS.ACTIVE }, { id: tripId })

    return successResponse(
      code.SUCCESS,
      "Status changed to Active successfully",
    )
  }

  private async sendTripNotificationToCustomer(
    tripId: number,
    messageKey: string,
    customerId: number[],
    drop_location?: string,
    status?: string,
    driverId?: number,
    driverName?: string,
  ) {
    let message = ""
    let title = ""
    if (messageKey === "otpVerified") {
      message = formatMessage("otpVerified", {
        DROP_LOCATION: drop_location,
      })
      title = "Trip started"
    } else if (messageKey === "tripEnd") {
      message = formatMessage("tripEnd", {})
      title = "Trip ended"
    }

    this.notificationService.sendCustomerNotification({
      user_ids: customerId,
      title,
      message,
      data: {
        trip_id: tripId,
        type: "trip",
        status: status,
        driver_id: driverId,
        driver_name: driverName,
      },
    })
  }
}
