const mongoose = require('mongoose');

class BaseModel {
  /**
   * Initialize the model with Mongoose
   * This is called automatically when the model is imported
   */
  static init() {
    if (!this.schema) {
      throw new Error(`Schema not defined for ${this.name} model`);
    }
    
    // Register the model with Mongoose if not already registered
    if (!mongoose.models[this.name]) {
      this._model = mongoose.model(this.name, this.schema);
    } else {
      this._model = mongoose.models[this.name];
    }
    
    return this._model;
  }
  static get schema() {
    throw new Error('schema getter must be implemented');
  }

  /**
   * Get the Mongoose model instance
   * Initializes the model if not already done
   */
  static get model() {
    if (!this._model) {
      return this.init(); // This will set this._model
    }
    return this._model;
  }

  static find(conditions = {}) {
    // Ensure model is initialized
    const model = this.model;
    return model.find(conditions);
  }

  /**
   * Paginate query results
   * @param {Object} query - The query object
   * @param {Object} options - Pagination options (page, limit, sort, select, populate, etc.)
   * @returns {Promise<Object>} Paginated results
   */
  static async paginate(query = {}, options = {}) {
    // Ensure model is initialized
    const model = this.model;
    
    // Set default options
    const page = parseInt(options.page, 10) || 1;
    const limit = parseInt(options.limit, 10) || 10;
    const sort = options.sort || { createdAt: -1 };
    const select = options.select || '';
    const populate = options.populate || [];
    const lean = options.lean || false;
    
    // Execute query with pagination
    const [docs, total] = await Promise.all([
      model.find(query)
        .select(select)
        .sort(sort)
        .skip((page - 1) * limit)
        .limit(limit)
        .populate(populate)
        .lean(lean),
      model.countDocuments(query)
    ]);
    
    const totalPages = Math.ceil(total / limit);
    
    return {
      docs,
      total,
      limit,
      page,
      totalPages,
      hasNextPage: page < totalPages,
      hasPrevPage: page > 1,
      nextPage: page < totalPages ? page + 1 : null,
      prevPage: page > 1 ? page - 1 : null
    };
  }
  
  static async findById(id, options = {}) {
    // Ensure model is initialized
    const model = this.model;
    const query = model.findById(id);
    
    if (options.populate) {
      query.populate(options.populate);
    }
    
    if (options.lean) {
      query.lean();
    }
    
    return query.exec();
  }
  
  /**
   * Create a new document
   * @param {Object} data - Document data
   * @returns {Promise<Document>} Created document
   */
  static async create(data) {
    // Ensure model is initialized
    const model = this.model;
    return model.create(data);
  }

  static async findOne(conditions = {}, options = {}) {
    // Ensure model is initialized
    const model = this.model;
    const query = model.findOne(conditions);
    
    if (options.select) {
      query.select(options.select);
    }
    
    if (options.populate) {
      query.populate(options.populate);
    }
    
    if (options.lean) {
      query.lean();
    }
    
    return query.exec();
  }

  static async find(conditions = {}, options = {}) {
    // Ensure model is initialized first
    const model = this.model;
    
    const {
      sort = { _id: -1 },
      skip = 0,
      limit = 100,
      select,
      populate,
      lean = true
    } = options;
    
    let query = model.find(conditions);
    
    if (sort) {
      query = query.sort(sort);
    }
    
    if (skip) {
      query = query.skip(skip);
    }
    
    if (limit) {
      query = query.limit(limit);
    }
    
    if (select) {
      query = query.select(select);
    }
    
    if (populate) {
      query = query.populate(populate);
    }
    
    if (lean) {
      query = query.lean();
    }
    
    return query.exec();
  }


  static async update(id, data, options = {}) {
    const { new: returnNew = true, ...updateOptions } = options;
    
    return this.model.findByIdAndUpdate(
      id,
      { $set: data },
      { new: returnNew, ...updateOptions }
    ).exec();
  }

  static async delete(id) {
    return this.model.findByIdAndDelete(id).exec();
  }

  static async softDelete(id) {
    return this.model.findByIdAndUpdate(
      id,
      { $set: { status: false, deletedAt: new Date() } },
      { new: true }
    ).exec();
  }
  
  static async count(conditions = {}) {
    return this.model.countDocuments(conditions).exec();
  }

  static async findOneAndUpdate(conditions, update, options = {}) {
    const {
      new: returnNew = true,
      runValidators = true,
      upsert = false,
      ...updateOptions
    } = options;
    
    return this.model.findOneAndUpdate(
      conditions,
      update,
      {
        new: returnNew,
        runValidators,
        upsert,
        ...updateOptions
      }
    ).exec();
  }

  static async aggregate(pipeline = []) {
    return this.model.aggregate(pipeline).exec();
  }

  static async paginate(conditions = {}, options = {}) {
    const {
      page = 1,
      limit = 10,
      sort = { _id: -1 },
      select,
      populate,
      lean = true
    } = options;
    
    const skip = (page - 1) * limit;
    const [items, total] = await Promise.all([
      this.find(conditions, { sort, skip, limit, select, populate, lean }),
      this.count(conditions)
    ]);
    
    const totalPages = Math.ceil(total / limit) || 1;
    
    return {
      items,
      total,
      totalPages,
      currentPage: page,
      hasNextPage: page < totalPages,
      hasPreviousPage: page > 1
    };
  }
}

module.exports = BaseModel;
