import type { IUserMethods, UserDoc } from "./user.schema"
import bcrypt from "bcrypt"
import { v4 as uuidv4 } from "uuid"
import { randomIdGenerator } from "@/helpers"
import type { PluginFastifyInstance } from "types"
import mongoose from "mongoose"

export const MODEL_BASENAME = "user"

const { Schema } = mongoose

const randomId = randomIdGenerator(64)

const SALT_FACTOR = 10

const userSchema = new Schema<UserDoc, any, IUserMethods>(
  {
    _id: {
      type: String,
      default: () => uuidv4(),
    },

    email: {
      type: String,
      required: true,
      unique: true,
      lowercase: true,
      trim: true,
    },

    account: {
      type: Schema.Types.String,
      ref: "raclette__core-account",
      default: null,
    },

    // owner is a required field accross the app, the user usually own themselves
    owner: {
      type: Schema.Types.String,
      ref: "raclette__core-user",
      default: null,
    },

    title: { type: String, default: null },

    firstname: { type: String, default: null },

    lastname: { type: String, default: null },

    email_verified: { type: Boolean, default: false },

    active: { type: Boolean, default: true },

    blocked: { type: Boolean, default: false },

    /* hashed password field */
    password: { type: String, trim: true },

    /* keep track of last password update */
    passwordUpdatedAt: { type: Date, default: null },

    /* signed terms, we also keep track in the logs of this event */
    signedTermsAt: { type: Date, default: null },

    /* reset token, required when user needs to reset password */
    resetToken: {
      type: String,
      index: true,
      default: null,
      unique: true,
      sparse: true,
    },

    /*
     * we need to keep track of last reset request, as the key should only be valid
     * for a certain time
     */
    resetDate: { type: Date, default: null },

    /* tags, note that tag and this have to belong to the same project */
    tags: {
      type: [Schema.Types.String],
      ref: "raclette__core-tag",
      default: [],
    },

    /* soft delete */
    isDeleted: { type: Boolean, default: false },

    /* settings */
    settings: {
      emailSignature: { type: String, default: null },
    },

    /* matches the configStore entry */
    ux: {
      lastProjectId: { type: String, default: null },
      browser: { type: [Schema.Types.Mixed], default: [] },
      project: { type: Schema.Types.Mixed, default: null },
    },
  },
  {
    timestamps: true,
    optimisticConcurrency: true,
  },
)

/*
 * Pre save
 *
 * Before saving the object we need to hash the password
 */
userSchema.pre("save", async function (done) {
  if (
    !this.isModified("password") ||
    this.password === undefined ||
    !this.password ||
    this.password === null
  ) {
    this.setResetToken()
    return done()
  }

  if (!this.resetToken || this.resetToken === "") {
    this.setResetToken()
  }

  try {
    this.password = await this.hashPassword(this.password)
    if (this.isModified("password")) {
      this.passwordUpdatedAt = new Date() // Now this is type-safe
    }
    return done()
  } catch (error) {
    return done(error as Error)
  }
})

// Index for common queries
userSchema.index({ email: 1, isDeleted: 1 })
userSchema.index({ isDeleted: 1 })

/*
 * Set Password method
 */
userSchema.methods.hashPassword = async (
  password: string,
): Promise<string | undefined> => {
  if (!password) {
    return undefined
  }

  try {
    const salt = await bcrypt.genSalt(SALT_FACTOR)
    return await bcrypt.hash(password, salt)
  } catch (error) {
    console.error("Error hashing password", error)
    throw error
  }
}

/*
 * check password method
 */
userSchema.methods.checkPassword = async function (
  guess: string,
): Promise<boolean> {
  if (!this.password) {
    throw new Error("Password is not set on the user object")
  }

  return bcrypt.compare(guess, this.password)
}

/*
 * set Reset Key
 * In case the user needs to reset his password, we need a temporary key
 */
userSchema.methods.setResetToken = function (): string {
  this.resetToken = randomId()
  this.resetDate = new Date() // Now this is type-safe
  return this.resetToken
}

export const createModels = (fastify: PluginFastifyInstance) => {
  return fastify.createModel(MODEL_BASENAME, userSchema)
}
