import { Injectable } from "@nestjs/common"
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 { errorMessage, isEmpty, successMessage } from "src/utils/helpers"
import { Between, Repository } from "typeorm"
import { UserLoginRepository } from "../../auth/repositories/user-login.repository"
import { TripRepository } from "../../trips/repositories/trip.repository"
import { VehicleManufacturerRepository } from "../../vehicle-manufacturer/repositories/vehicle-manufacturer.repository"
import { VehicleModelRepository } from "../../vehicle-model/repositories/vehicle-model.repository"
import { CreateFleetManagementDto } from "../dto/create-fleet-management.dto"
import { FindAllFleetManagementDto } from "../dto/find-all-fleet-management.dto"
import { FleetTripFilterDto } from "../dto/fleet-trip-filter.dto"
import { UpdateFleetManagementDto } from "../dto/update-fleet-management.dto"
import { FleetManagement } from "../entities/fleet-management.entity"
import { DriverFleetHistoryRepository } from "../repositories/driver-fleet-history.repository"
import { FleetManagementRepository } from "../repositories/fleet-management.repository"
import { VehicleStatusService } from "../../vehicle-status/v1/vehicle-status.service"
import { VEHICLE_STATUS } from "../../vehicle-status/entities/vehicle-status.entity"
import { TripFleetAssignment } from "../../trips/entities/fleet-trip-management.entity"
import { STATUS } from "src/constants/trip.constant"

@Injectable()
export class FleetManagementService {
  constructor(
    private readonly fleetManagementRepository: FleetManagementRepository,
    private vehicleModelRepository: VehicleModelRepository,
    private readonly vehicleManufacturerRepository: VehicleManufacturerRepository,
    private readonly driverFleetHistoryRepository: DriverFleetHistoryRepository,
    private readonly loginRepository: UserLoginRepository,
    private readonly tripRepository: TripRepository,
    private readonly userLoginRepository: UserLoginRepository,

    private readonly vehicleStatusService: VehicleStatusService,

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

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

  async checkAddOnExistsVehicle(
    registration_number: string,
    vin_number: string,
    excludeId?: number,
  ): Promise<boolean> {
    const whereCondition: any = {}
    const whereLowerCondition: any = {
      registration_number,
      vin_number,
    }

    if (excludeId) {
      whereCondition["id"] = {
        not: excludeId,
      }
    }

    const vehicle = await this.fleetManagementRepository.getByParams({
      where: whereCondition,
      orWhere: whereLowerCondition,
      findOne: true,
    })

    return !isEmpty(vehicle)
  }

  async create(
    createFleetManagementDto: CreateFleetManagementDto,
    registrationFile?: Express.Multer.File,
  ) {
    const lastFleet: any = await this.fleetManagementRepository.getByParams({
      orderBy: {
        id: "DESC",
      },
      findOne: true,
    })

    let newCarCode = "CAR001"
    if (lastFleet?.car_code) {
      const lastCodeNumber = parseInt(lastFleet.car_code.replace("CAR", ""), 10)
      const nextCodeNumber = lastCodeNumber + 1
      newCarCode = `CAR${nextCodeNumber.toString().padStart(3, "0")}`
    }

    createFleetManagementDto.car_code = newCarCode

    const existingVehicle = await this.checkAddOnExistsVehicle(
      createFleetManagementDto.registration_number,
      createFleetManagementDto.vin_number,
    )

    if (existingVehicle) {
      return failureResponse(code.VALIDATION, messageKey.already_exist)
    }

    const VehicleManufacturer =
      await this.vehicleManufacturerRepository.getByParams({
        where: {
          id: createFleetManagementDto?.vehicle_manufacture_id,
        },
        findOne: true,
      })

    if (isEmpty(VehicleManufacturer)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, {
          ":data": "vehicle manufacture",
        }),
      )
    }

    const unassignedVehicleStatus =
      await this.vehicleStatusService.getStatusFromName(
        VEHICLE_STATUS.Unassigned,
      )
    createFleetManagementDto.vehicle_status_id = unassignedVehicleStatus?.id

    const vehicleModel = await this.vehicleModelRepository.getByParams({
      where: { id: createFleetManagementDto.vehicle_model_id },
      findOne: true,
    })

    if (isEmpty(vehicleModel)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, {
          ":data": "vehicle model",
        }),
      )
    }

    if (!isEmpty(createFleetManagementDto?.assigned_driver)) {
      const isDriverAlreadyHasVehicle =
        await this.fleetManagementRepository.getByParams({
          where: {
            assigned_driver: createFleetManagementDto?.assigned_driver,
          },
          findOne: true,
        })

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

    const fleetManagement = new FleetManagement()
    Object.assign(fleetManagement, createFleetManagementDto)

    if (registrationFile) {
      const relativePath = registrationFile.path
        .replace(/\\/g, "/")
        .replace(/^public\//, "")
      fleetManagement.registration_document = relativePath
    }

    const vehicleStatus = await this.vehicleStatusService.getStatusFromName(
      VEHICLE_STATUS.Unassigned,
    )
    if (vehicleStatus) {
      const vehicleStatusId = vehicleStatus.id
      fleetManagement.vehicle_status_id = vehicleStatusId
    }

    await this.fleetManagementRepository.save(fleetManagement)

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

  async findAll(token: any, query: FindAllFleetManagementDto) {
    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"],
    })

    if (userDetail?.user?.role?.name.toLowerCase() === "dispatcher") {
      query.assigned_dispatcher_id = userDetail.user?.team_member_id
    }
    const { data, count } =
      await this.fleetManagementRepository.findAllWithDriverDetails(query)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Fleet Management",
      }),
      { count, data },
    )
  }

  async findOne(id: number) {
    const fleetManagement = (await this.fleetManagementRepository.getByParams({
      where: { id },
      findOne: true,
      relations: [
        "vehicle_manufacture:id,name",
        "vehicleModel:id,name",
        "vehicle_type:id,name",
        "vehicle_status:id,name",
        "assignedDriver:id,first_name,last_name,email,country_code,phone_number",
        "assigned_dispatcher:id,first_name,last_name,email,country_code,phone_number",
        "state_details:id,name",
        "vehicle_location:id,name",
        "vehicle_city_location:id,name",
      ],
      select: [
        "id",
        "car_code",
        "year",
        "color",
        "passenger_capacity",
        "registration_number",
        "registration_start_date",
        "registration_expiry_date",
        "vin_number",
        "odo_reader",
        "unit",
        "vehicle_description",
        "vehicle_ownership",
        "owner_information",
        "registration_document",
        "created_at",
        "updated_at",
      ],
    })) as any

    if (!fleetManagement) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, {
          ":data": "Fleet Management",
        }),
      )
    }

    // const baseURL = process.env.BACK_URL

    // if (fleetManagement.registration_document) {
    //   fleetManagement.registration_document =
    //     `${baseURL}/` + fleetManagement.registration_document
    // } else {
    //   fleetManagement.registration_document = ""
    // }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Fleet Management",
      }),
      fleetManagement,
    )
  }

  async update(
    id: number,
    updateFleetManagementDto: UpdateFleetManagementDto,
    registrationFile?: Express.Multer.File,
  ) {
    const fleetManagement: any =
      await this.fleetManagementRepository.getByParams({
        where: { id },
        findOne: true,
      })

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

    const existingVehicle = await this.checkAddOnExistsVehicle(
      updateFleetManagementDto.registration_number,
      updateFleetManagementDto.vin_number,
      id,
    )

    if (existingVehicle) {
      return failureResponse(code.VALIDATION, messageKey.already_exist)
    }

    if (!isEmpty(updateFleetManagementDto?.vehicle_status_id)) {
      const unassignedStatus =
        await this.vehicleStatusService.getStatusFromName(
          VEHICLE_STATUS.Unassigned,
        )

      const underMaintenanceStatus =
        await this.vehicleStatusService.getStatusFromName(
          VEHICLE_STATUS.Under_Maintenance,
        )

      const currentStatusId = updateFleetManagementDto.vehicle_status_id

      if (
        currentStatusId === unassignedStatus?.id ||
        currentStatusId === underMaintenanceStatus?.id
      ) {
        const activeTripAssignment = await this.tripFleetAssignmentRepository
          .createQueryBuilder("assignment")
          .innerJoinAndSelect("assignment.trip", "trip")
          .where("assignment.fleet_id = :fleetId", {
            fleetId: fleetManagement.id,
          })
          .andWhere("assignment.trip_completed_at IS NULL")
          .andWhere("trip.status IN (:...statuses)", {
            statuses: [STATUS.PICKUP, STATUS.ONGOING],
          })
          .getOne()

        if (activeTripAssignment) {
          return failureResponse(
            code.BAD_REQUEST,
            messageKey.fleet_assigned_to_active_trip,
          )
        }

        const driverFleetHistory: any =
          await this.driverFleetHistoryRepository.getByParams({
            where: {
              fleet_id: fleetManagement.id,
            },
            whereNull: ["released_at"],
            orderBy: {
              assigned_at: "DESC",
            },
            findOne: true,
          })

        if (!isEmpty(driverFleetHistory)) {
          driverFleetHistory.released_at = new Date()
          await this.driverFleetHistoryRepository.save(driverFleetHistory)
        }
        updateFleetManagementDto.assigned_driver = null
      }
    }

    Object.assign(fleetManagement, updateFleetManagementDto)

    if (registrationFile) {
      const relativePath = registrationFile.path
        .replace(/\\/g, "/")
        .replace(/^public\//, "")
      fleetManagement.registration_document = relativePath
    }

    const data = await this.fleetManagementRepository.save(fleetManagement)

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

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

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

    await this.fleetManagementRepository.remove({ id })

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

  async getRegisterNumberDropdown(token: any) {
    const user: any = await this.loginRepository.getByParams({
      where: {
        access_token: token,
      },
      findOne: true,
      relations: ["user:id,team_member_id.team_members:id,reporting_to_id"],
    })

    const fleetManagement: any =
      await this.fleetManagementRepository.getByParams({
        where: {
          assigned_driver: user?.user?.team_member_id,
        },
        findOne: true,
        select: ["id", "registration_number"],
        relations: ["vehicle_status:id,name"],
      })

    const data = (await this.fleetManagementRepository.getByParams({
      where: {
        assigned_dispatcher_id: user?.user?.team_members?.reporting_to_id,
      },
      whereNull: ["deleted_at", "assigned_driver"],
      select: ["id", "registration_number"],
      findOne: false,
      relations: ["vehicle_status:id,name"],
    })) as any

    const excludedStatuses = ["Under Maintenance", "Reserved"]

    const filteredData = data.filter((item) => {
      return !excludedStatuses.includes(item?.vehicle_status?.name)
    })

    const fleetList = [
      ...(fleetManagement &&
      !excludedStatuses.includes(fleetManagement?.vehicle_status?.name)
        ? [fleetManagement]
        : []),
      ...filteredData,
    ]

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Fleet Management",
      }),
      fleetList,
    )
  }

  async addRegistrationDocument(
    id: number,
    registrationFile?: Express.Multer.File,
  ) {
    const document = (await this.fleetManagementRepository.getByParams({
      where: { id },
      findOne: true,
    })) as any

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

    if (registrationFile) {
      const relativePath = registrationFile.path
        .replace(/\\/g, "/")
        .replace(/^public\//, "")

      document.registration_document = relativePath
    }

    await this.fleetManagementRepository.save(document)

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

  async assignFleetByDriver(id: number, driverId: number) {
    const fleetManagement = (await this.fleetManagementRepository.getByParams({
      where: { id },
      findOne: true,
    })) as FleetManagement

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

    // find last active assignment for driver
    const lastAssignment = (await this.driverFleetHistoryRepository.getByParams(
      {
        where: { driver_id: driverId },
        whereNull: ["released_at"],
        findOne: true,
      },
    )) as any

    const now = new Date()
    const startOfDay = new Date(now.setHours(0, 0, 0, 0))
    const endOfDay = new Date(now.setHours(23, 59, 59, 999))

    // check if already assigned today
    const existingAssignment =
      (await this.driverFleetHistoryRepository.getByParams({
        where: {
          driver_id: driverId,
          assigned_at: Between(startOfDay, endOfDay) as any,
        },
        whereNull: ["released_at"],
        findOne: true,
      })) as any

    // release today's existing assignment if any
    if (existingAssignment) {
      existingAssignment.released_at = new Date()
      await this.driverFleetHistoryRepository.save(existingAssignment)

      // nullify that fleet's assigned driver
      if (existingAssignment?.fleet_id) {
        const oldFleet = (await this.fleetManagementRepository.getByParams({
          where: { id: existingAssignment.fleet_id },
          findOne: true,
        })) as any

        if (oldFleet) {
          const vehicleStatus =
            await this.vehicleStatusService.getStatusFromName(
              VEHICLE_STATUS.Unassigned,
            )
          if (vehicleStatus) {
            const vehicleStatusId = vehicleStatus.id
            oldFleet.vehicle_status_id = vehicleStatusId
          }
          oldFleet.assigned_driver = null
          await this.fleetManagementRepository.save(oldFleet)
        }
      }
    }

    // also release the last assignment fleet (previous fleet, not only today’s)
    if (lastAssignment?.fleet_id && lastAssignment.fleet_id !== id) {
      const prevFleet = (await this.fleetManagementRepository.getByParams({
        where: { id: lastAssignment.fleet_id },
        findOne: true,
      })) as any

      if (prevFleet) {
        const vehicleStatus = await this.vehicleStatusService.getStatusFromName(
          VEHICLE_STATUS.Unassigned,
        )
        if (vehicleStatus) {
          const vehicleStatusId = vehicleStatus.id
          prevFleet.vehicle_status_id = vehicleStatusId
        }
        prevFleet.assigned_driver = null
        await this.fleetManagementRepository.save(prevFleet)
      }
    }

    const isSameFleetAsPrevious = lastAssignment?.fleet_id === id ? true : false

    // assign new fleet
    fleetManagement.assigned_driver = driverId
    const vehicleStatus = await this.vehicleStatusService.getStatusFromName(
      VEHICLE_STATUS.Operational,
    )
    if (vehicleStatus) {
      const vehicleStatusId = vehicleStatus.id
      fleetManagement.vehicle_status_id = vehicleStatusId
    }

    await this.fleetManagementRepository.save(fleetManagement)

    await this.driverFleetHistoryRepository.save({
      fleet_id: id,
      driver_id: driverId,
      assigned_at: new Date(),
    })

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, {
        ":data": "Fleet Management",
      }),
      {
        isSameFleetAsPrevious,
        previous_fleet_id: lastAssignment?.fleet_id || null,
        current_fleet_id: id,
      },
    )
  }

  async getAllVehiclesForTripAssignment(
    tripId: number,
    fleetTripFilterDto: FleetTripFilterDto,
  ) {
    const { vehicle_type_id: vehicleTypeId, skip, limit } = fleetTripFilterDto

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

    const underMaintenanceStatus =
      await this.vehicleStatusService.getStatusFromName(
        VEHICLE_STATUS.Under_Maintenance,
      )

    const { pickup_datetime, dropoff_datetime } = trip

    const dispatcherId = trip?.customer?.dispatcher_id

    const query = this.fleetEntityRepository
      .createQueryBuilder("fleet")
      .leftJoinAndSelect("fleet.assignedDriver", "driver")
      .leftJoinAndSelect("fleet.vehicle_manufacture", "manufacturer")
      .leftJoinAndSelect("fleet.vehicleModel", "model")
      .where("fleet.vehicle_type_id = :vehicleTypeId", { vehicleTypeId })
      .andWhere("fleet.assigned_dispatcher_id = :dispatcherId", {
        dispatcherId,
      })
      .andWhere("fleet.vehicle_status_id != :underMaintenanceStatusId", {
        underMaintenanceStatusId: underMaintenanceStatus.id,
      })

      .andWhere(
        `
  fleet.id NOT IN (
    SELECT assignment.fleet_id
    FROM trip_fleet_assignments assignment
    INNER JOIN trips trip
      ON trip.id = assignment.trip_id
     AND trip.deleted_at IS NULL
    WHERE assignment.fleet_id IS NOT NULL
      AND assignment.trip_completed_at IS NULL
      AND trip.status != 'cancelled'
      AND trip.id != :currentTripId
      AND (
            trip.pickup_datetime < :dropoff_datetime
            AND trip.dropoff_datetime > :pickup_datetime
          )
  )
  `,
      )
      .setParameters({
        pickup_datetime,
        dropoff_datetime,
        currentTripId: tripId, // Pass current trip id here
      })

      .andWhere(
        `
  fleet.id NOT IN (
    SELECT m.fleet_id
    FROM vehicle_maintenances m
    WHERE m.next_scheduled_maintenance IS NOT NULL
      AND m.status = 'scheduled'
      AND m.next_scheduled_maintenance::date BETWEEN :pickup_date::date AND :dropoff_date::date
  )
  `,
      )
      .setParameters({
        pickup_date: pickup_datetime,
        dropoff_date: dropoff_datetime,
      })
      .skip(skip)
      .take(limit)

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

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

  async checkFleetAvailability(payload: { fleet_id: number; trip_id: number }) {
    const { fleet_id, trip_id } = payload

    /* 1️⃣ Fetch trip */
    const trip: any = await this.tripRepository.getByParams({
      where: { id: trip_id },
      findOne: true,
    })

    if (!trip) {
      throw new Error("Trip not found")
    }

    const { pickup_datetime, dropoff_datetime } = trip

    /* 2️⃣ Fetch fleet with assigned driver */
    const fleet: any = await this.fleetManagementRepository.getByParams({
      where: { id: fleet_id },
      findOne: true,
      relations: ["assignedDriver", "assignedDriver.languages"],
    })

    if (!fleet) {
      throw new Error("Fleet not found")
    }

    const assignedDriver = fleet.assignedDriver || null

    /* Helper: normalize driver for frontend */
    const normalizeDriver = (driver: any) =>
      driver
        ? {
            id: driver.id,
            first_name: driver.first_name,
            last_name: driver.last_name,
            phone_number: driver.phone_number,
            country_code: driver.country_code,
            languages: driver.languages || [],
          }
        : null

    /* 3️⃣ No assigned driver → available */
    if (!assignedDriver) {
      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, { ":data": "Trips" }),
        {
          is_available: true,
          driver: null,
        },
      )
    }

    /* 4️⃣ Check overlapping active trip */
    const overlappingAssignment = await this.tripFleetAssignmentRepository
      .createQueryBuilder("tfa")
      .innerJoin("tfa.trip", "trip")
      .innerJoin("tfa.driver", "driver")
      .select([
        "tfa.id",
        "driver.id",
        "driver.first_name",
        "driver.last_name",
        "driver.phone_number",
        "driver.country_code",
      ])
      .where("tfa.driver_id = :driverId", { driverId: assignedDriver.id })
      .andWhere("tfa.trip_completed_at IS NULL")
      .andWhere("tfa.trip_id != :tripId", { tripId: trip_id })
      .andWhere("trip.status != :cancelledStatus", {
        cancelledStatus: STATUS.CANCELLED,
      })
      .andWhere(
        ":pickup < trip.dropoff_datetime AND :dropoff > trip.pickup_datetime",
        { pickup: pickup_datetime, dropoff: dropoff_datetime },
      )
      .getOne()

    /* 5️⃣ Driver busy → not available */
    if (overlappingAssignment) {
      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, { ":data": "Trips" }),
        {
          is_available: false,
          driver: normalizeDriver(overlappingAssignment.driver),
        },
      )
    }

    /* 6️⃣ Driver free → available */
    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Trips" }),
      {
        is_available: true,
        driver: normalizeDriver(assignedDriver),
      },
    )
  }
}
