import { Injectable } from "@nestjs/common"
import { ConfigService } from "@nestjs/config"
import * as bcrypt from "bcrypt"
import * as crypto from "crypto"
import moment from "moment"
import { sendOtpEmailTemplate } from "src/common/emails/templates/send-otp"
import { sendOtpViaSms } from "src/common/sms/send-otp-twilio"
import { messages } from "src/constants/messages"
import { mailSubject } from "../../../common/emails/email-subjects"
import { changePasswordIntimation } from "../../../common/emails/templates/change-password-intimation"
import { forgotPasswordToken } from "../../../common/emails/templates/forgot-password"
import { fileStoreLocation } from "../../../common/file-upload/file-store-location"
import {
  failureResponse,
  successResponse,
} from "../../../common/response/response"
import { code } from "../../../common/response/response.code"
import { appSetting } from "../../../config/app.config"
import { messageKey } from "../../../constants/message-keys"
import {
  currentTimestamp,
  encryptPassword,
  errorMessage,
  findMinutes,
  generateResponseObject,
  isEmpty,
  sendEmailNotification,
  successMessage,
  validationMessage,
} from "../../../utils/helpers"
import { RolePermission } from "../../role-permission/entities/role-permission.entity"
import { RolePermissionRepository } from "../../role-permission/repositories/role-permission.repository"
import { loginResponse, profileResponse } from "../auth.remove.columns"
import { ChangePasswordDto } from "../dto/change-password.dto"
import { CreateUserDto } from "../dto/create-user.dto"
import { EditProfileDto } from "../dto/edit-profile.dto"
import { LoginDto } from "../dto/login.dto"
import { MobileLoginDto } from "../dto/mobile-login.dto"
import { ResetPasswordDto } from "../dto/reset-password.dto"
import { Auth } from "../entities/auth.entity"
import { UserLogin } from "../entities/user-login.entity"
import { UserOldPassword } from "../entities/user-old-password.entity"
import { AuthRepository } from "../repositories/auth.repository"
import { UserOldPasswordRepository } from "../repositories/old-passwords.repository"
import { UserLoginRepository } from "../repositories/user-login.repository"
import { RoleRepository } from "../../role/repositories/role.repository"

@Injectable()
export class AuthService {
  constructor(
    private readonly authRepository: AuthRepository,
    private readonly userLoginRepository: UserLoginRepository,
    private readonly userOldPasswordRepository: UserOldPasswordRepository,
    private readonly configService: ConfigService,
    private readonly rolePermissionRepository: RolePermissionRepository,
    private readonly roleRepository: RoleRepository,
  ) {}

  async login(loginDto: LoginDto) {
    const user: any = await this.authRepository.getByParams({
      whereLower: {
        email: loginDto.email?.trim() || "",
      },
      relations: ["role"],
      findOne: true,
    })

    if (!isEmpty(user)) {
      const isUserActive = await this.checkUserStatus(user.id)
      if (!isUserActive) {
        // User is inactive, return failure
        return failureResponse(422, messageKey.inactive_user)
      }

      if (!isEmpty(user.account_locked_at)) {
        const minutes = findMinutes(user.account_locked_at)
        const remainingMinutes = appSetting.can_login_after_locked - minutes

        if (minutes < appSetting.can_login_after_locked) {
          return failureResponse(
            code.ACCOUNT_LOCKED,
            errorMessage(messageKey.temp_account_locked, {
              ":minutes": remainingMinutes,
            }),
          )
        }
      }

      const isPasswordValid = await this.comparePassword(
        loginDto.password,
        user.password,
      )

      if (isPasswordValid) {
        const userExistingLogin = (await this.userLoginRepository.getByParams({
          where: {
            user_id: user.id,
            device_type: loginDto.device_type,
          },
          findOne: true,
        })) as UserLogin

        if (user.role.is_web_login === false) {
          return failureResponse(500, messageKey.unauthorized)
        }

        let accessToken = this.generateToken()
        let refreshToken = this.generateRefreshToken()
        const accessTokenExpiry: any = moment
          .utc()
          .add(appSetting.access_token_expiry_in_minutes, "minutes")
          .format()
        const refreshTokenExpiry = this.getRefreshTokenExpiry()

        if (!isEmpty(userExistingLogin)) {
          userExistingLogin.access_token = accessToken
          userExistingLogin.device_type = loginDto.device_type
          userExistingLogin.access_token_expire_at = accessTokenExpiry
          userExistingLogin.refresh_token = refreshToken
          userExistingLogin.refresh_token_expire_at = refreshTokenExpiry
          userExistingLogin.fcm_token = loginDto.fcm_token
          userExistingLogin.deleted_at = null

          await this.userLoginRepository.save(userExistingLogin, {
            id: userExistingLogin.id,
          })
          accessToken = userExistingLogin.access_token
          refreshToken = userExistingLogin.refresh_token
        } else {
          const userLogin = new UserLogin()
          userLogin.user_id = user.id
          userLogin.device_id = loginDto.device_id
          userLogin.access_token = accessToken
          userLogin.device_type = loginDto.device_type
          userLogin.access_token_expire_at = accessTokenExpiry
          userLogin.refresh_token = refreshToken
          userLogin.refresh_token_expire_at = refreshTokenExpiry
          userLogin.fcm_token = loginDto.fcm_token
          userLogin.deleted_at = null

          await this.userLoginRepository.save(userLogin)
        }

        user.access_token = accessToken
        await this.authRepository.save(user)

        let accessibleModules = []

        if (user?.role && user?.role?.id) {
          const rolePermissions =
            (await this.rolePermissionRepository.getByParams({
              where: {
                role_id: user.role.id,
              },
              relations: ["module", "permission"],
            })) as RolePermission[]

          accessibleModules = rolePermissions.map((rp) => ({
            module_id: rp.module.id,
            module_type: rp.module.module_type,
            permission_id: rp.permission.id,
            permission_name: rp.permission.permission_type,
          }))
        }

        user.access_token = accessToken
        user.last_login_at = currentTimestamp()
        user.account_locked_at = null
        user.invalid_password_attempt = 0
        await this.authRepository.save(user)

        const userObj = {
          ...generateResponseObject(user, loginResponse),
          role: user.role,
          modules: accessibleModules,
          refresh_token: refreshToken,
          refresh_token_expire_at: refreshTokenExpiry,
        }

        return successResponse(
          code.SUCCESS,
          successMessage(messageKey.login_success),
          userObj,
        )
      } else {
        user.invalid_password_attempt += 1
        let responseCode = code.INVALID_CREDENTIALS

        let message: string = errorMessage(messageKey.invalid_credentials)

        if (
          user.invalid_password_attempt ===
          appSetting.max_invalid_password_attempt
        ) {
          responseCode = code.ACCOUNT_LOCKED
          user.account_locked_at = currentTimestamp().unix()
          ;(message = errorMessage(messageKey.temp_account_locked, {
            ":minutes": appSetting.can_login_after_locked,
          })),
            {
              minutes: appSetting.can_login_after_locked,
            }
        }
        await this.authRepository.save(user)

        return failureResponse(responseCode, message)
      }
    }

    const message = errorMessage(messageKey.invalid_credentials)
    return failureResponse(code.INVALID_CREDENTIALS, message)
  }

  async forgotPassword(email: string) {
    const user: any = await this.authRepository.getByParams({
      whereLower: {
        email: email?.trim() || "",
      },
      relations: ["role"],
      findOne: true,
    })

    if (!user) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.user_not_found),
      )
    }

    if (user.role.is_web_login === false) {
      return failureResponse(500, messageKey.unauthorized)
    }

    const isUserActive = await this.checkUserStatus(user.id)
    if (!isUserActive) {
      return failureResponse(code.UNAUTHORIZED, messageKey.inactive_user)
    }

    // Check if user already requested a reset token recently
    if (user.password_reset_token_generated_at) {
      const minutes = findMinutes(user.password_reset_token_generated_at)
      if (minutes < appSetting.can_request_token_after_minutes) {
        const remainingMinutes =
          appSetting.can_request_token_after_minutes - minutes
        return failureResponse(
          code.ALREADY_EMAIL_SEND,
          validationMessage(messageKey.otp_already_send, {
            ":minute": remainingMinutes,
            ":label": remainingMinutes === 1 ? "minute" : "minutes",
          }),
        )
      }
    }
    // Generate new reset token
    const generatePasswordResetToken = (): string => {
      return crypto.randomBytes(32).toString("hex")
    }

    const resetToken = generatePasswordResetToken()

    // Update user with new reset token
    user.password_reset_token = resetToken
    user.password_reset_token_generated_at = currentTimestamp().unix()
    user.updated_at = currentTimestamp()

    await this.authRepository.save(user)

    sendEmailNotification(
      email,
      forgotPasswordToken(resetToken),
      mailSubject.forgotPasswordSubject,
    )

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.password_reset_code),
    )
  }

  async resetPassword(token: string, resetPasswordDto: ResetPasswordDto) {
    const existingUser = (await this.authRepository.getByParams({
      whereLower: {
        email: resetPasswordDto.email?.trim() || "",
      },
      relations: ["role"],
      findOne: true,
    })) as any

    if (isEmpty(existingUser)) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.user_not_found),
      )
    }

    if (existingUser?.role?.is_web_login === false) {
      return failureResponse(500, messageKey.unauthorized)
    }

    const user: any = await this.authRepository.getByParams({
      whereLower: {
        email: resetPasswordDto.email?.trim() || "",
      },
      where: {
        password_reset_token: token,
      },
      findOne: true,
    })

    if (!user?.email) {
      return failureResponse(
        code.DATA_NO_LONGER_AVAILABLE,
        validationMessage(messageKey.user_not_found),
      )
    }

    if (!user?.password_reset_token) {
      return failureResponse(
        code.DATA_NO_LONGER_AVAILABLE,
        validationMessage(messageKey.token_expire),
      )
    }

    const tokenAgeMinutes = findMinutes(user?.password_reset_token_generated_at)
    if (tokenAgeMinutes > appSetting.can_request_token_after_minutes) {
      return failureResponse(
        code.DATA_NO_LONGER_AVAILABLE,
        validationMessage(messageKey.token_expire),
      )
    }

    const isLastPasswordUsed = await this.checkLastPassword(
      user.id,
      resetPasswordDto.password,
    )

    if (isLastPasswordUsed) {
      return failureResponse(
        code.PASSWORD_ALREADY_USED,
        validationMessage(messageKey.password_already_used),
      )
    }

    const encryptedPassword = await encryptPassword(resetPasswordDto.password)

    await this.userOldPasswordRepository.save({
      user_id: user.id,
      password: user.password,
    })

    user.password = encryptedPassword
    user.password_reset_token = null
    user.password_reset_token_generated_at = null
    user.updated_at = currentTimestamp()
    await this.authRepository.save(user)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.password_update),
    )
  }

  async create(createUserDto: CreateUserDto) {
    await this.authRepository.save(createUserDto)
  }

  async getProfile(token: string) {
    const loggedIdUser: any = await this.getUserByToken(token)

    if (isEmpty(loggedIdUser)) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.user_not_found),
      )
    }

    const user: any = await this.authRepository.getByParams({
      where: {
        id: loggedIdUser.user_id,
      },
      findOne: true,
    })

    if (isEmpty(user) || !isEmpty(user.account_locked_at)) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.user_not_found),
      )
    }
    const userObj = generateResponseObject(user, profileResponse)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.profile_retrieve),
      userObj,
    )
  }

  async editProfile(
    token: string,
    editProfileDto: EditProfileDto,
    attachment: any,
  ) {
    const loggedInUser: any = await this.getUserByToken(token)
    if (isEmpty(loggedInUser)) {
      return failureResponse(
        code.SUCCESS,
        validationMessage(messageKey.user_not_found),
      )
    }

    const user: any = await this.authRepository.getByParams({
      where: {
        id: loggedInUser.user_id,
      },
      findOne: true,
    })

    const originalContactNo = user.contact_no

    if (attachment) {
      const attachmentUrl = `${fileStoreLocation.profile_pic}/${attachment.filename}`
      user.profile_pic = attachmentUrl
    }

    user.first_name = editProfileDto.first_name
    user.last_name = editProfileDto.last_name
    user.contact_no = editProfileDto.contact_no

    await this.authRepository.save(user)

    // If mobile login identifier (contact number) changed, logout from all devices
    if (
      typeof editProfileDto.contact_no !== "undefined" &&
      editProfileDto.contact_no !== originalContactNo
    ) {
      await this.logoutAllSessionsByUserId(user.id)
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.profile_update),
    )
  }

  async changePassword(token: string, changePasswordDto: ChangePasswordDto) {
    const loggedInUser = await this.getUserByToken(token)
    if (isEmpty(loggedInUser)) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        validationMessage(messageKey.user_not_found),
      )
    }

    const user: any = await this.authRepository.getByParams({
      where: {
        id: loggedInUser.user_id,
      },
      findOne: true,
    })

    if (
      !(await this.comparePassword(
        changePasswordDto.old_password,
        user.password,
      ))
    ) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.old_password_incorrect),
      )
    }

    const isAlreadyUsedPassword: boolean = await this.checkLastPassword(
      user.id,
      changePasswordDto.new_password,
    )
    if (isAlreadyUsedPassword) {
      return failureResponse(
        code.PASSWORD_ALREADY_USED,
        validationMessage(messageKey.password_already_used),
      )
    }

    const encryptedPassword = await encryptPassword(
      changePasswordDto.new_password,
    )

    const authUser = new Auth()
    authUser.password = encryptedPassword

    await this.authRepository.save(authUser, { id: user.id })

    const userOldPassword = new UserOldPassword()

    userOldPassword.user_id = user.id
    userOldPassword.password = encryptedPassword

    await this.userOldPasswordRepository.save(userOldPassword)
    sendEmailNotification(
      user.email,
      changePasswordIntimation((user.first_name + " " + user.last_name).trim()),
      mailSubject.changePasswordSubject,
    )

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.password_update),
    )
  }

  /**
   * Retrieves a user by their access token.
   *
   * @param {string} token - The access token of the user.
   * @return {Promise<User | null>} A Promise that resolves to the user object if found, or null if not found.
   */
  async getUserByToken(token: string): Promise<any> {
    const userLogin = await this.userLoginRepository.getByParams({
      where: {
        access_token: token,
      },
      relations: ["user:id,team_member_id"],
      findOne: true,
    })

    if (userLogin) {
      return userLogin
    }

    return null
  }

  private async checkUserStatus(userId: number): Promise<boolean> {
    const user = (await this.authRepository.getByParams({
      where: { id: userId },
      findOne: true,
    })) as any
    if (!user || user.status === "inactive") {
      return false // Inactive or not found
    }
    return true // Active
  }

  private comparePassword = async (
    password: string,
    hashedPassword: string,
  ) => {
    return await bcrypt.compare(password, hashedPassword)
  }

  private async checkLastPassword(userId: number, newPassword: string) {
    const skip = this.configService.get("APP.pagination.skip")
    const lastThreePasswords: any =
      await this.userOldPasswordRepository.getByParams({
        where: {
          user_id: userId,
        },
        take: 3,
        skip: skip,
        orderBy: {
          id: "DESC",
        },
      })

    if (lastThreePasswords && !isEmpty(lastThreePasswords?.data)) {
      let passwordExist: boolean = false
      for (const passwordObj of lastThreePasswords.data) {
        if (await this.comparePassword(newPassword, passwordObj.password)) {
          passwordExist = true
          break
        }
      }

      return passwordExist
    }
  }

  /**
   * Generates a random string of a specified length using the specified characters.
   *
   * @param {number} length - The length of the random string to generate.
   * @return {string} The randomly generated string.
   */
  private generateRandomString(length: number): string {
    let result = ""
    const characters =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+=-[]{}|;:,.<>?"
    for (let i = 0; i < length; i++) {
      result += characters.charAt(Math.floor(Math.random() * characters.length))
    }
    return result
  }

  public generateToken(): string {
    // Generate a new token for the admin
    const randomString = this.generateRandomString(200)
    const encodedString = Buffer.from(randomString).toString("base64")
    const timestamp = moment().format()
    const stringWithTimestamp = encodedString + timestamp.toString()
    return Buffer.from(stringWithTimestamp).toString("base64")
  }

  public generateRefreshToken(): string {
    return crypto.randomBytes(64).toString("hex")
  }

  private getRefreshTokenExpiry(): Date {
    return moment.utc().add(7, "days").toDate()
  }

  async refreshAccessToken(refreshToken: string) {
    try {
      const userLogin =
        await this.userLoginRepository.findByRefreshToken(refreshToken)

      if (!userLogin) {
        return failureResponse(code.UNAUTHORIZED, messageKey.invalid_token)
      }

      if (userLogin.refresh_token_expire_at < new Date()) {
        await this.userLoginRepository.revokeRefreshToken(refreshToken)
        return failureResponse(
          code.UNAUTHORIZED,
          validationMessage(messageKey.token_expire),
        )
      }

      if (userLogin.user.status === "inactive") {
        return failureResponse(code.UNAUTHORIZED, messageKey.inactive_user)
      }

      const newAccessToken = this.generateToken()
      const newRefreshToken = this.generateRefreshToken()
      const accessTokenExpiry = moment
        .utc()
        .add(appSetting.access_token_expiry_in_minutes, "minutes")
        .format()
      const refreshTokenExpiry = this.getRefreshTokenExpiry()

      await this.userLoginRepository.updateTokens(
        userLogin.id,
        newAccessToken,
        newRefreshToken,
        accessTokenExpiry,
        refreshTokenExpiry,
      )

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.login_success),
        {
          access_token: newAccessToken,
          access_token_expire_at: accessTokenExpiry,
          refresh_token: newRefreshToken,
          refresh_token_expire_at: refreshTokenExpiry,
        },
      )
    } catch (error) {
      return failureResponse(code.ERROR, messageKey.something_went_wrong)
    }
  }

  async logout(token: string) {
    const loggedInUser = await this.getUserByToken(token)
    if (isEmpty(loggedInUser)) {
      return failureResponse(
        code.SUCCESS,
        validationMessage(messageKey.user_not_found),
      )
    }

    await this.userLoginRepository.remove({ id: loggedInUser.id })
    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.logout_success),
    )
  }

  /**
   * Logout user from all active mobile sessions (android/ios) by user id.
   * Web sessions (email/password) remain active.
   */
  async logoutAllSessionsByUserId(userId: number): Promise<void> {
    await this.userLoginRepository.removeMobileSessionsByUserId(userId)
  }

  async checkAdminExist(email: string) {
    const user: any = await this.authRepository.getByParams({
      whereLower: {
        email: email?.trim() || "",
      },
    })

    return user.length > 0
  }

  // Apis for mobile app
  private checkRoleAuthorization(userType: string, roleName: string): boolean {
    switch (userType) {
      case "driver":
        return roleName === "driver"
      case "dispatcher":
        return roleName === "dispatcher"
      case "customer":
        return roleName === "customer"
      default:
        return false
    }
  }

  async mobileLogin(username: MobileLoginDto) {
    let users: any
    let user: any

    // First, fetch all users matching email or phone
    if (username.email) {
      users = await this.authRepository.getByParams({
        whereLower: { email: username.email?.trim() || "" },
        findOne: false, // Get all matching users
        relations: [
          "role:id,name",
          "team_members:id.id_proofs:id,document_type,document_number,expiry_date,document_files",
        ],
      })
    } else if (username.contact_no) {
      users = await this.authRepository.getByParams({
        where: {
          country_code: username.country_code,
          contact_no: username.contact_no,
        },
        findOne: false, // Get all matching users
        relations: [
          "role:id,name",
          "team_members:id,first_name,last_name.id_proofs:id,document_type,document_number,expiry_date,document_files",
        ],
      })
    }

    // Filter by role if multiple users exist
    if (Array.isArray(users) && users.length > 0) {
      if (username.role && users.length > 1) {
        user = users.find(
          (u) => u.role?.name?.toLowerCase() === username.role.toLowerCase(),
        )
      } else {
        user = users[0]
      }
    } else {
      user = users
    }

    if (isEmpty(user)) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.user_not_found),
      )
    }

    if (user) {
      const isUserActive = await this.checkUserStatus(user.id)
      if (!isUserActive) {
        return failureResponse(code.UNAUTHORIZED, messageKey.inactive_user)
      }
    }

    const userType = username.role?.toLowerCase()
    const roleName = user.role?.name?.toLowerCase()

    const isAuthorized = this.checkRoleAuthorization(userType, roleName)
    if (!isAuthorized) {
      return failureResponse(code.FORBIDDEN, messages.error.unauthorized)
    }

    let otp: string
    let expiresAt: Date

    if (
      (roleName === "driver" &&
        user.email?.trim()?.toLowerCase() === "anderson87@yopmail.com") ||
      (roleName === "dispatcher" &&
        user.email?.trim()?.toLowerCase() === "nancy@mailinator.com")
    ) {
      // Fixed OTP for dispatcher and longer expiry if desired
      otp = "123456"
      expiresAt = new Date(Date.now() + 10 * 60 * 1000)
    } else {
      otp = Math.floor(100000 + Math.random() * 900000).toString()
      expiresAt = new Date(Date.now() + 2 * 60 * 1000)
    }

    user.otp = otp
    user.otp_expires_at = expiresAt

    const idProofs =
      user.team_members?.id_proofs?.map((proof) => ({
        id: proof.id,
        document_type: proof.document_type,
        document_number: proof.document_number,
        document_file: proof.document_file,
      })) ?? []

    const hasDrivingLicense =
      user.role?.name === "Driver" &&
      idProofs.some((proof) => proof.document_type === "driving-license")

    if (hasDrivingLicense) {
      user.is_document = true
    }

    await this.authRepository.save(user, { otp: otp })

    user.device_id = username.device_id
    user.device_type = username.device_type
    user.os_version = username.os_version

    const accessToken = this.generateToken()
    const refreshToken = this.generateRefreshToken()
    const accessTokenExpiry = moment
      .utc()
      .add(appSetting.access_token_expiry_in_minutes, "minutes")
      .format()
    const refreshTokenExpiry = this.getRefreshTokenExpiry()

    if (user) {
      // Check if user already has an existing login session for this device
      const existingLogin = (await this.userLoginRepository.getByParams({
        where: {
          user_id: user.id,
          device_type: username.device_type,
        },
        findOne: true,
      })) as UserLogin

      if (existingLogin) {
        await this.userLoginRepository.save(
          {
            ...existingLogin,
            device_type: username.device_type,
            device_token: username.device_token,
            os_version: username.os_version,
            access_token: accessToken,
            access_token_expire_at: accessTokenExpiry,
            refresh_token: refreshToken,
            refresh_token_expire_at: refreshTokenExpiry,
            deleted_at: null,
          },
          { id: existingLogin.id },
        )
      } else {
        // Create new session
        await this.userLoginRepository.save({
          user_id: user.id,
          device_id: username.device_id,
          device_type: username.device_type,
          device_token: username.device_token,
          os_version: username.os_version,
          access_token: accessToken,
          access_token_expire_at: accessTokenExpiry,
          refresh_token: refreshToken,
          refresh_token_expire_at: refreshTokenExpiry,
          deleted_at: null,
        })
      }
    }

    if (username.email) {
      // User logged in with email, send OTP only via email
      sendEmailNotification(
        user.email,
        sendOtpEmailTemplate(otp),
        mailSubject.otpSubject,
      )
    } else if (username.contact_no) {
      // User logged in with phone, send OTP only via SMS
      try {
        if (user.contact_no && user.country_code) {
          const phone = `${user.country_code}${user.contact_no}`
          await sendOtpViaSms(phone, otp)
        }
      } catch (smsError) {
        console.log("SMS sending failed:", smsError.message)
      }
    }

    return successResponse(code.SUCCESS, successMessage(messageKey.otp_sent), {
      user_id: user.id,
      is_document: user.is_document,
    })
  }

  async verifyOtp(otp: string, user_id: number, fcm_token?: string) {
    const user = (await this.authRepository.getByParams({
      where: {
        id: user_id,
        otp: otp,
      },
      findOne: true,
      relations: [
        "role:id,name",
        "team_members:id.id_proofs:id,document_type,document_number,expiry_date,document_files",
      ],
      select: [
        "id",
        "email",
        "first_name",
        "last_name",
        "contact_no",
        "country_code",
        "profile_pic",
        "status",
        "otp",
        "otp_expires_at",
        "role",
        "team_members",
      ],
    })) as any

    if (isEmpty(user)) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_otp),
      )
    }

    if (user.otp_expires_at < new Date()) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_otp),
      )
    }

    user.otp = null
    user.otp_expires_at = null

    const accessToken = this.generateToken()
    const refreshToken = this.generateRefreshToken()
    const accessTokenExpiry = moment
      .utc()
      .add(appSetting.access_token_expiry_in_minutes, "minutes")
      .format()
    const refreshTokenExpiry = this.getRefreshTokenExpiry()

    const existingLogin = (await this.userLoginRepository.getByParams({
      where: {
        user_id: user.id,
      },
      whereIn: {
        device_type: ["android", "ios"],
      },
      findOne: true,
    })) as UserLogin

    // Update with FCM token
    await this.userLoginRepository.save(
      {
        access_token: accessToken,
        access_token_expire_at: accessTokenExpiry,
        refresh_token: refreshToken,
        refresh_token_expire_at: refreshTokenExpiry,
        ...(fcm_token && { fcm_token }), // Add FCM token if provided in header
      },
      { id: existingLogin.id },
    )

    await this.authRepository.save(user)

    const responseData = {
      id: user.id,
      team_memeer_id: user.team_members?.id,
      first_name: user.first_name,
      last_name: user.last_name,
      email: user.email,
      country_code: user.country_code,
      contact_no: user.contact_no,
      profile_pic: user.profile_pic,
      status: user.status,
      is_document: user.is_document,
      role: user.role,
      id_proofs: user.team_members?.id_proofs || [],
      access_token: accessToken,
      access_token_expire_at: accessTokenExpiry,
      refresh_token: refreshToken,
      refresh_token_expire_at: refreshTokenExpiry,
      fcm_token,
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.otp_verified),
      responseData,
    )
  }

  async getFcmTokens(user_ids: number[]): Promise<{
    success: boolean
    code: number
    message: string
    data?: UserLogin[]
  }> {
    try {
      const usersFcmTokens = (await this.userLoginRepository.getByParams({
        where: {},
        whereIn: {
          user_id: user_ids,
        },
        select: [
          "id",
          "user_id",
          "fcm_token",
          "device_id",
          "device_type",
          "device_token",
        ],
      })) as Pick<
        UserLogin,
        | "id"
        | "user_id"
        | "fcm_token"
        | "device_id"
        | "device_type"
        | "device_token"
      >[]

      return successResponse(
        code.SUCCESS,
        messageKey.data_retrieve,
        usersFcmTokens,
      )
    } catch (error) {
      return failureResponse(code.ERROR, messageKey.something_went_wrong)
    }
  }

  async removeFcmTokens(fcmTokens: string[]): Promise<any> {
    if (!fcmTokens || fcmTokens.length === 0) {
      return
    }
    try {
      await this.userLoginRepository.removeFcmTokens(fcmTokens)
      return successResponse(code.SUCCESS, "FCM tokens removed successfully")
    } catch (error) {
      console.error("Error removing FCM tokens:", error)
      return
    }
  }

  async getAdmins(): Promise<Auth[]> {
    const users = (await this.authRepository["entity"]
      .createQueryBuilder("user")
      .leftJoinAndSelect("user.role", "role")
      .where("LOWER(role.name) = :roleName", { roleName: "admin" })
      .andWhere("user.status = :status", { status: "active" })
      .select([
        "user.id",
        "user.first_name",
        "user.last_name",
        "user.email",
        "user.status",
        "user.contact_no",
        "user.country_code",
        "role.name",
      ])
      .getMany()) as Auth[]

    return users
  }

  async getSuperAdmins(): Promise<Auth[]> {
    const users = (await this.authRepository["entity"]
      .createQueryBuilder("user")
      .leftJoinAndSelect("user.role", "role")
      .where("role.name ILIKE :roleName", { roleName: "%super admin%" })
      .andWhere("user.status = :status", { status: "active" })
      .select([
        "user.id",
        "user.first_name",
        "user.last_name",
        "user.email",
        "user.status",
        "user.contact_no",
        "user.country_code",
        "role.name",
      ])
      .getMany()) as Auth[]

    return users
  }
}
