const mongoose = require('mongoose');
const { isEmail, isMobilePhone } = require('validator');
const bcrypt = require('bcryptjs');
const mongoosePaginate = require('mongoose-paginate-v2');
const ApiError = require('../utils/ApiError');
const httpStatus = require('http-status');

const deliveryBoySchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'Name is required'],
    trim: true,
    maxlength: [100, 'Name cannot exceed 100 characters']
  },
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    trim: true,
    lowercase: true,
    validate: [isEmail, 'Please enter a valid email']
  },
  phone: {
    type: String,
    required: [true, 'Phone number is required'],
    validate: {
      validator: (value) => isMobilePhone(value, 'any', { strictMode: false }),
      message: 'Please enter a valid phone number'
    }
  },
  password: {
    type: String,
    required: [true, 'Password is required'],
    minlength: [6, 'Password must be at least 6 characters long'],
    select: false
  },
  store: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Store',
    required: false
  },
  isAvailable: {
    type: Boolean,
    default: true
  },
  status: {
    type: String,
    enum: {
      values: ['pending', 'approved', 'rejected'],
      message: 'Status must be one of: pending, approved, or rejected'
    },
    default: 'pending'
  },
  rating: {
    type: Number,
    min: [0, 'Rating cannot be less than 0'],
    max: [5, 'Rating cannot be more than 5'],
    default: 0,
    set: (val) => Math.round(val * 10) / 10 // Round to 1 decimal place
  },
  totalDeliveries: {
    type: Number,
    default: 0,
    min: 0
  },
  completedDeliveries: {
    type: Number,
    default: 0,
    min: 0
  },
  cancelledDeliveries: {
    type: Number,
    default: 0,
    min: 0
  },
  location: {
    type: {
      type: String,
      enum: ['Point'],
      default: 'Point'
    },
    coordinates: {
      type: [Number],
      default: [0, 0],
      validate: {
        validator: function(v) {
          return v.length === 2 && 
                 v[0] >= -180 && v[0] <= 180 && 
                 v[1] >= -90 && v[1] <= 90;
        },
        message: 'Invalid coordinates. Must be [longitude, latitude]'
      }
    }
  },
  lastOnline: {
    type: Date,
    default: Date.now
  },
  isVerified: {
    type: Boolean,
    default: false
  },
  isActive: {
    type: Boolean,
    default: true,
    select: false
  },
  documents: [{
    type: {
      type: String,
      enum: {
        values: ['aadhar', 'license', 'rc', 'insurance', 'other'],
        message: 'Invalid document type'
      },
      required: [true, 'Document type is required']
    },
    url: {
      type: String,
      required: [true, 'Document URL is required']
    },
    publicId: String,
    verified: {
      type: Boolean,
      default: false
    },
    verifiedAt: Date,
    _id: false
  }],
  isActive: {
    type: Boolean,
    default: true
  },
  deletedAt: {
    type: Date,
    select: false
  }
}, {
  timestamps: true,
  toJSON: { 
    virtuals: true,
    transform: function(doc, ret) {
      ret.id = ret._id;
      delete ret._id;
      delete ret.__v;
      delete ret.password;
      return ret;
    }
  },
  toObject: { 
    virtuals: true,
    transform: function(doc, ret) {
      ret.id = ret._id;
      delete ret._id;
      delete ret.__v;
      delete ret.password;
      return ret;
    }
  }
});

// Indexes for better performance
deliveryBoySchema.index({ location: '2dsphere' });
deliveryBoySchema.index({ store: 1, status: 1, isAvailable: 1 });
deliveryBoySchema.index({ email: 1 }, { unique: true });
deliveryBoySchema.index({ phone: 1 }, { unique: true });
deliveryBoySchema.index({ 'documents.type': 1, 'documents.verified': 1 });
deliveryBoySchema.index({ name: 'text', email: 'text', phone: 'text' });

// Virtuals
deliveryBoySchema.virtual('successRate').get(function() {
  return this.totalDeliveries > 0 
    ? Math.round((this.completedDeliveries / this.totalDeliveries) * 100) 
    : 0;
});

deliveryBoySchema.virtual('averageDeliveryTime').get(function() {
  // This would be calculated based on actual delivery data
  return null;
});

// Instance method to check if password matches
// This is used during login to verify the password
deliveryBoySchema.methods.matchPassword = async function(enteredPassword) {
  return await bcrypt.compare(enteredPassword, this.password);
};

// Document middleware
deliveryBoySchema.pre('save', async function(next) {
  try {
    // Only run this function if password was actually modified
    if (!this.isModified('password')) return next();
    
    // If password is not provided and this is a new document, generate a random password
    if ((!this.password || this.password.length < 6) && this.isNew) {
      this.password = Math.random().toString(36).slice(-8); // Generate random 8-char password
    }
    
    // Hash the password with cost of 12
    if (this.password) {
      this.password = await bcrypt.hash(this.password, 12);
    }
    
    next();
  } catch (error) {
    next(error);
  }
});

// Query middleware
deliveryBoySchema.pre(/^find/, function(next) {
  // Only include active delivery boys by default
  if (this.getQuery().isActive === undefined) {
    this.find({ isActive: { $ne: false } });
  }
  next();
});

// Static methods
deliveryBoySchema.statics.createDeliveryBoy = async function(deliveryBoyData) {
  try {
    // Check if email already exists
    const existingEmail = await this.findOne({ email: deliveryBoyData.email });
    if (existingEmail) {
      throw new ApiError(
        409, // CONFLICT
        'Email already in use',
        [{ field: 'email', message: 'This email is already registered' }]
      );
    }

    // Check if phone already exists
    const existingPhone = await this.findOne({ phone: deliveryBoyData.phone });
    if (existingPhone) {
      throw new ApiError(
        409, // CONFLICT
        'Phone number already in use',
        [{ field: 'phone', message: 'This phone number is already registered' }]
      );
    }

    // Hash password if provided
    if (deliveryBoyData.password) {
      const salt = await bcrypt.genSalt(10);
      deliveryBoyData.password = await bcrypt.hash(deliveryBoyData.password, salt);
    }

    // Create the delivery boy
    const deliveryBoy = new this({
      ...deliveryBoyData,
      isActive: true,
      status: deliveryBoyData.status || 'pending', // Ensure status is set
      isAvailable: deliveryBoyData.isAvailable !== undefined ? deliveryBoyData.isAvailable : true
    });
    
    // Save the delivery boy
    await deliveryBoy.save();
    
    // Remove sensitive data
    const deliveryBoyObj = deliveryBoy.toObject();
    delete deliveryBoyObj.password;
    
    return deliveryBoyObj;
  } catch (error) {
    console.error('Error creating delivery boy:', error);
    if (error.code === 11000) {
      // Handle duplicate key error
      const field = Object.keys(error.keyPattern)[0];
      throw new ApiError(409, `${field} already exists`); // CONFLICT
    }
    throw error;
  }
};

deliveryBoySchema.statics.findAllActive = async function(options = {}) {
  const { 
    page = 1, 
    limit = 10, 
    search, 
    store,
    status,
    isAvailable,
    sortBy = 'createdAt',
    sortOrder = 'desc'
  } = options;
  
  const skip = (page - 1) * limit;
  const sort = { [sortBy]: sortOrder === 'desc' ? -1 : 1 };

  let query = { isActive: true };
  
  if (search) {
    query.$or = [
      { name: { $regex: search, $options: 'i' } },
      { email: { $regex: search, $options: 'i' } },
      { phone: { $regex: search, $options: 'i' } }
    ];
  }

  if (store) {
    query.store = store;
  }

  if (status) {
    query.status = status;
  }

  if (isAvailable !== undefined) {
    query.isAvailable = isAvailable === 'true';
  }

  const [data, total] = await Promise.all([
    this.find(query)
      .populate('store', 'name address')
      .sort(sort)
      .skip(skip)
      .limit(parseInt(limit))
      .lean(),
    this.countDocuments(query)
  ]);

  return {
    data,
    pagination: {
      total,
      page: parseInt(page),
      limit: parseInt(limit),
      pages: Math.ceil(total / limit)
    }
  };
};

deliveryBoySchema.statics.findByIdWithDetails = async function(deliveryBoyId) {
  if (!mongoose.Types.ObjectId.isValid(deliveryBoyId)) {
    const error = new Error('Invalid delivery boy ID');
    error.statusCode = 400;
    throw error;
  }

  const deliveryBoy = await this.findById(deliveryBoyId)
    .populate('store', 'name address')
    .lean();

  if (!deliveryBoy) {
    const error = new Error('No delivery boy found with that ID');
    error.statusCode = 404;
    throw error;
  }

  return deliveryBoy;
};

deliveryBoySchema.statics.updateDeliveryBoy = async function(deliveryBoyId, updateData) {
  if (!mongoose.Types.ObjectId.isValid(deliveryBoyId)) {
    const error = new Error('Invalid delivery boy ID');
    error.statusCode = 400;
    throw error;
  }

  // Don't allow updating email or phone directly
  if (updateData.email || updateData.phone) {
    const error = new Error('Email and phone cannot be updated this way');
    error.statusCode = 400;
    throw error;
  }

  // If updating password, hash it first
  if (updateData.password) {
    updateData.password = await bcrypt.hash(updateData.password, 12);
  }

  const updatedDeliveryBoy = await this.findByIdAndUpdate(
    deliveryBoyId,
    { $set: updateData },
    { new: true, runValidators: true }
  ).lean();

  if (!updatedDeliveryBoy) {
    const error = new Error('No delivery boy found with that ID');
    error.statusCode = 404;
    throw error;
  }

  return updatedDeliveryBoy;
};

deliveryBoySchema.statics.deleteDeliveryBoy = async function(deliveryBoyId) {
  if (!mongoose.Types.ObjectId.isValid(deliveryBoyId)) {
    const error = new Error('Invalid delivery boy ID');
    error.statusCode = 400;
    throw error;
  }

  // Check if there are any active orders assigned to this delivery boy
  const activeOrders = await mongoose.model('Order').countDocuments({
    deliveryBoy: deliveryBoyId,
    status: { $in: ['assigned', 'out_for_delivery'] }
  });

  if (activeOrders > 0) {
    const error = new Error('Cannot delete delivery boy with active orders');
    error.statusCode = 400;
    throw error;
  }

  const deletedDeliveryBoy = await this.findByIdAndDelete(deliveryBoyId);
  
  if (!deletedDeliveryBoy) {
    const error = new Error('No delivery boy found with that ID');
    error.statusCode = 404;
    throw error;
  }

  return deletedDeliveryBoy;
};

// Find available delivery boys near a location
deliveryBoySchema.statics.findAvailableNear = async function(coordinates, maxDistance = 5000) {
  return this.find({
    location: {
      $near: {
        $geometry: {
          type: 'Point',
          coordinates: [parseFloat(coordinates[0]), parseFloat(coordinates[1])]
        },
        $maxDistance: maxDistance // in meters
      }
    },
    isAvailable: true,
    status: 'active',
    isActive: true
  });
};

// Add pagination plugin
deliveryBoySchema.plugin(mongoosePaginate);

// Create and export the model
const DeliveryBoy = mongoose.model('DeliveryBoy', deliveryBoySchema);

module.exports = DeliveryBoy;
