const BaseModel = require('./BaseModel');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const ErrorResponse = require('../utils/errorResponse');

const storeSchema = new mongoose.Schema({
  _id: {
    type: mongoose.Schema.Types.ObjectId,
    required: true
  },
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  name: {
    type: String,
    required: [true, 'Store owner name is required'],
    trim: true
  },
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    trim: true,
    lowercase: true,
    match: [/^[^\s@]+@[^\s@]+\.[^\s@]+$/, 'Please provide a valid email address']
  },
  mobile: {
    type: String,
    required: [true, 'Mobile number is required'],
    trim: true,
    match: [/^[0-9]{10}$/, 'Please provide a valid 10-digit mobile number']
  },
  store: {
    storeName: {
      type: String,
      required: [true, 'Store name is required'],
      trim: true
    },
    categoryIds: [{
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Category'
    }],
    productStatus: {
      type: String,
      enum: ['Active', 'Inactive', 'Pending'],
      default: 'Active'
    },
    logo: String,
    nationalIdCard: String,
    // Location data for geospatial queries
    location: {
      type: {
        type: String,
        enum: ['Point'],
        required: true,
        default: 'Point'
      },
      coordinates: {
        type: [Number], // [longitude, latitude]
        required: true,
        validate: {
          validator: function(v) {
            return v.length === 2 && 
                   v[0] >= -180 && v[0] <= 180 && 
                   v[1] >= -90 && v[1] <= 90;
          },
          message: props => `${props.value} is not a valid coordinate pair [longitude, latitude]`
        }
      },
      address: String,
      city: String,
      state: String,
      country: String,
      pincode: String,
      formattedAddress: String
    },
    // Delivery radius in meters
    deliveryRadius: {
      type: Number,
      required: true,
      default: 3000, // 3km default delivery radius
      min: [500, 'Delivery radius must be at least 500 meters'],
      max: [10000, 'Delivery radius cannot exceed 10km']
    },
    isActive: {
      type: Boolean,
      default: true
    },
    addressProof: String,
    taxName: String,
    taxNumber: String,
    panNumber: String,
    commission: {
      type: Number,
      min: 0,
      max: 100,
      default: 0
    },
    description: String,
    stockAlertSettings: {
      outOfStockThreshold: {
        type: Number,
        default: 5,
        min: 0
      },
      lowStockThreshold: {
        type: Number,
        default: 10,
        min: 0
      }
    }
  },
  location: {
    street: {
      type: String,
      required: [true, 'Street address is required'],
      trim: true
    },
    city: {
      type: String,
      required: [true, 'City is required'],
      trim: true
    },
    state: {
      type: String,
      required: [true, 'State is required'],
      trim: true
    },
    country: {
      type: String,
      required: [true, 'Country is required'],
      trim: true,
      default: 'India'
    },
    pincode: {
      type: String,
      required: [true, 'Pincode is required'],
      trim: true,
      match: [/^[0-9]{6}$/, 'Please provide a valid 6-digit pincode']
    },
    landmark: String,
    locationPoint: {
      type: {
        type: String,
        enum: ['Point'],
        default: 'Point'
      },
      coordinates: {
        type: [Number],
        default: [0, 0]
      }
    }
  },
  settings: {
    requireProductApproval: {
      type: Boolean,
      default: false
    },
    viewCustomerDetails: {
      type: Boolean,
      default: false
    }
  },
  status: {
    type: String,
    enum: ['Active', 'Inactive', 'Pending', 'Suspended'],
    default: 'Active'
  },
  user: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  storeManager: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  }
}, {
  timestamps: true,
  toJSON: { virtuals: true },
  toObject: { virtuals: true }
});

// Create indexes
storeSchema.index({ 'store.location': '2dsphere' });
storeSchema.index({ email: 1 }, { unique: true });
storeSchema.index({ mobile: 1 }, { unique: true });
storeSchema.index({ 'store.storeName': 'text' });
storeSchema.index({ 'location.city': 1 });
storeSchema.index({ status: 1 });

// Virtual for store URL
storeSchema.virtual('storeUrl').get(function() {
  return `/store/${this._id}`;
});

// Static method to find all active stores with pagination and search
storeSchema.statics.findAllActive = async function(options = {}) {
  const {
    page = 1,
    limit = 10,
    search,
    status,
    city
  } = options;

  const query = {};
  
  if (status) {
    query.status = status;
  }
  
  if (city) {
    query['location.city'] = new RegExp(city, 'i');
  }
  
  if (search) {
    query.$or = [
      { 'store.storeName': new RegExp(search, 'i') },
      { name: new RegExp(search, 'i') },
      { email: new RegExp(search, 'i') },
      { mobile: new RegExp(search, 'i') }
    ];
  }

  const skip = (page - 1) * limit;
  const [items, total] = await Promise.all([
    this.find(query)
      .populate('user', 'name email')
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .lean(),
    this.countDocuments(query)
  ]);

  return {
    items,
    total,
    currentPage: page,
    totalPages: Math.ceil(total / limit),
    hasNextPage: page * limit < total,
    hasPreviousPage: page > 1
  };
};

// Static method to find store by ID with details
storeSchema.statics.findByIdWithDetails = async function(id) {
  return this.findById(id)
    .populate('user', 'name email')
    .populate('store.categoryIds', 'name')
    .lean();
};

// Static method to update store
storeSchema.statics.updateSeller = async function(id, updateData, options = {}) {
  const store = await this.findByIdAndUpdate(
    id,
    updateData,
    {
      new: true,
      runValidators: true,
      ...options
    }
  ).populate('user', 'name email');

  return store;
};

// Static method to update store status
storeSchema.statics.updateStatus = async function(id, status) {
  return this.findByIdAndUpdate(
    id,
    { status },
    {
      new: true,
      runValidators: true
    }
  ).populate('user', 'name email');
};

// Static method to delete store and associated user
storeSchema.statics.delete = async function(id) {
  const isProduction = process.env.NODE_ENV === 'production';
  const session = isProduction ? await mongoose.startSession() : null;
  
  if (isProduction) await session.startTransaction();
  
  try {
    // First delete the store
    const store = isProduction 
      ? await this.findByIdAndDelete(id).session(session)
      : await this.findByIdAndDelete(id);
    
    if (!store) {
      throw new Error('Store not found');
    }
    
    // Delete the associated user
    const User = require('./User');
    if (isProduction) {
      await User.findByIdAndDelete(id).session(session);
    } else {
      await User.findByIdAndDelete(id);
    }
    
    if (isProduction) await session.commitTransaction();
    
    return store;
  } catch (error) {
    if (isProduction) await session.abortTransaction();
    throw error;
  } finally {
    if (isProduction) session.endSession();
  }
};

// Static method to search stores
storeSchema.statics.searchSellers = async function(query, options = {}) {
  const {
    page = 1,
    limit = 10
  } = options;

  const searchQuery = {
    $or: [
      { 'store.storeName': new RegExp(query, 'i') },
      { name: new RegExp(query, 'i') },
      { email: new RegExp(query, 'i') },
      { mobile: new RegExp(query, 'i') },
      { 'location.city': new RegExp(query, 'i') },
      { 'location.state': new RegExp(query, 'i') }
    ]
  };

  const skip = (page - 1) * limit;
  const [items, total] = await Promise.all([
    this.find(searchQuery)
      .populate('user', 'name email')
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit)
      .lean(),
    this.countDocuments(searchQuery)
  ]);

  return {
    items,
    total,
    currentPage: page,
    totalPages: Math.ceil(total / limit),
    hasNextPage: page * limit < total,
    hasPreviousPage: page > 1
  };
};

// Static method to get store statistics
storeSchema.statics.getSellerStatistics = async function() {
  const stats = await this.aggregate([
    {
      $group: {
        _id: '$status',
        count: { $sum: 1 }
      }
    }
  ]);

  const total = await this.countDocuments();
  
  return {
    total,
    byStatus: stats.reduce((acc, curr) => {
      acc[curr._id] = curr.count;
      return acc;
    }, {})
  };
};

// Static method to get stores by city
storeSchema.statics.getSellersByCity = async function(city) {
  return this.find({
    'location.city': new RegExp(city, 'i'),
    status: 'Active'
  })
    .populate('user', 'name email')
    .sort({ createdAt: -1 })
    .lean();
};

// Static method to create a new store with a user account
storeSchema.statics.createStoreWithUser = async function(storeData) {
  const isProduction = process.env.NODE_ENV === 'production';
  const session = isProduction ? await mongoose.startSession() : null;
  
  if (isProduction) await session.startTransaction();
  
  try {
    const { email, password, mobile, name, ...storeInfo } = storeData;
    
    if (!email || !password || !mobile || !name) {
      throw new Error('Required fields are missing');
    }

    // Check if email already exists in User or Store collection
    const [existingUser, existingStore] = await Promise.all([
      this.model('User').findOne({ email }),
      this.findOne({ email })
    ]);
    
    if (existingUser || existingStore) {
      throw new Error('Email already exists in the system');
    }

    // 1. First create the User
    const User = this.model('User');
    const userId = new mongoose.Types.ObjectId();
    const userData = {
      _id: userId,
      name: storeInfo.store?.storeName || name,
      email: email.toLowerCase(),
      phone: mobile,
      password: password,
      role: 'store_manager',
      status: storeInfo.status === 'active' || storeInfo.status === 'active',
      location: storeInfo.location?.locationPoint ? {
        type: 'Point',
        coordinates: storeInfo.location.locationPoint.coordinates,
        isDefault: true,
        lastUpdated: new Date()
      } : undefined
    };

    let user, store;
    
    if (isProduction) {
      user = await User.create([userData], { session });
    } else {
      user = await User.create([userData]);
    }
    
    if (!user || user.length === 0) {
      throw new Error('Failed to create user account');
    }

    // 2. Create the Store with the same _id as User
    const storeDataWithUser = {
      _id: user[0]._id,  // Use the same ID as User
      user: user[0]._id, // Reference to the User
      storeManager: user[0]._id, // Set storeManager to the same user ID
      name: name,
      email: email.toLowerCase(),
      mobile: mobile,
      ...storeInfo,
      status: storeInfo.status || 'active',
      // Ensure store object exists to prevent errors
      store: {
        ...(storeInfo.store || {}),
        storeName: storeInfo.store?.storeName || name
      },
      // Initialize location if not provided
      location: storeInfo.location || {
        street: '',
        city: '',
        state: '',
        country: 'India',
        pincode: '',
        locationPoint: {
          type: 'Point',
          coordinates: [0, 0]
        }
      }
    };

    if (isProduction) {
      store = await this.create([storeDataWithUser], { session });
    } else {
      store = await this.create([storeDataWithUser]);
    }
    
    if (!store || store.length === 0) {
      // Clean up user if store creation fails
      if (isProduction) {
        await User.deleteOne({ _id: userId }).session(session);
      } else {
        await User.deleteOne({ _id: userId });
      }
      throw new Error('Failed to create store profile');
    }

    if (isProduction) {
      await session.commitTransaction();
    }
    
    // Return both user and store details
    return {
      user: user[0],
      store: store[0]
    };
  } catch (error) {
    if (isProduction) {
      await session.abortTransaction();
    }
    throw error;
  } finally {
    if (isProduction) {
      session.endSession();
    }
  }
};

// Update the existing update method to handle user updates if needed
storeSchema.statics.updateStore = async function(id, updateData) {
  const isProduction = process.env.NODE_ENV === 'production';
  const session = isProduction ? await mongoose.startSession() : null;
  
  if (isProduction) await session.startTransaction();
  
  try {
    const { email, mobile, name, storeManager, ...storeData } = updateData;
    
    // Update store data
    const updateOptions = { new: true };
    if (isProduction) updateOptions.session = session;
    
    // Prepare update object
    const updateObj = { ...storeData };
    
    // Only update storeManager if explicitly provided
    if (storeManager) {
      updateObj.storeManager = storeManager;
    }
    
    const store = await this.findByIdAndUpdate(
      id,
      updateObj,
      updateOptions
    );

    if (!store) {
      throw new Error('Store not found');
    }

    // Update corresponding user data if needed
    if (email || mobile || name) {
      const User = require('./User');
      const userUpdate = {};
      
      if (email) userUpdate.email = email.toLowerCase();
      if (mobile) userUpdate.phone = mobile;
      if (name) userUpdate.name = name;
      
      const userUpdateOptions = { new: true };
      if (isProduction) userUpdateOptions.session = session;
      
      await User.findByIdAndUpdate(
        id, // Same ID as store
        userUpdate,
        userUpdateOptions
      );
    }

    if (isProduction) await session.commitTransaction();
    return store;
  } catch (error) {
    if (isProduction) await session.abortTransaction();
    throw error;
  } finally {
    if (isProduction) session.endSession();
  }
};

// Pre-remove hook to delete associated user when store is deleted
storeSchema.pre('deleteOne', { document: true, query: false }, async function(next) {
  const session = this.$session();
  try {
    const User = require('./User');
    await User.deleteOne({ _id: this._id }).session(session);
    next();
  } catch (error) {
    next(error);
  }
});

/**
 * Find stores near a location within a maximum distance
 * @param {Object} location - GeoJSON Point { type: 'Point', coordinates: [longitude, latitude] }
 * @param {number} maxDistance - Maximum distance in meters
 * @param {Object} options - Query options (pagination, etc.)
 * @returns {Promise<Object>} - Object containing stores and pagination info
 */
storeSchema.statics.findNearbyStores = async function(location, maxDistance = 5000, options = {}) {
  const { page = 1, limit = 10 } = options;
  const skip = (page - 1) * limit;

  try {
    // Extract coordinates from location object
    let lat, lng;
    
    // Handle both { coordinates: [lng, lat] } and direct [lng, lat] formats
    if (location && location.coordinates && Array.isArray(location.coordinates) && location.coordinates.length === 2) {
      [lng, lat] = location.coordinates;
    } else if (Array.isArray(location) && location.length === 2) {
      [lng, lat] = location;
    } else {
      throw new ErrorResponse('Invalid location format. Expected { coordinates: [lng, lat] } or [lng, lat]', 400);
    }
    
    // Convert to numbers
    lng = parseFloat(lng);
    lat = parseFloat(lat);
    
    // Validate coordinate ranges
    if (isNaN(lng) || isNaN(lat) || 
        lng < -180 || lng > 180 || 
        lat < -90 || lat > 90) {
      throw new ErrorResponse('Invalid coordinate values. Longitude must be between -180 and 180, latitude between -90 and 90', 400);
    }

    // First, get all active stores
    const allStores = await this.find({
      'store.isActive': true,  // Check isActive inside store object
      status: 'Active'        // Check status at root level
    })
    .select('name email mobile store.storeName store.logo store.description store.location store.deliveryRadius store.isActive')
    .lean();

    console.log(`Found ${allStores.length} active stores`);

    // Calculate distance for each store and filter by maxDistance
    console.log(`Searching for stores near [${lat}, ${lng}] within ${maxDistance}m`);
    
    const storesWithDistance = allStores
      .map(store => {
        if (!store.store?.location?.coordinates) {
          console.log('Store missing location data:', store._id);
          return null;
        }
        
        const [storeLng, storeLat] = store.store.location.coordinates;
        console.log(`Checking store at [${storeLat}, ${storeLng}]`);
        
        const distance = this.calculateDistance(lat, lng, storeLat, storeLng);
        console.log(`Distance: ${distance}m`);
        
        // Format the store data to match frontend expectations
        const storeData = {
          _id: store._id.toString(),
          name: store.store.storeName,
          email: store.email,
          mobile: store.mobile,
          address: {
            display: store.store.location.formattedAddress || 
                    `${store.store.location.address || ''}, ${store.store.location.city || ''}, ${store.store.location.state || ''} ${store.store.location.pincode || ''}`.trim(),
            line1: store.store.location.address || '',
            city: store.store.location.city || '',
            state: store.store.location.state || '',
            country: store.store.location.country || 'India',
            postalCode: store.store.location.pincode || '',
            coordinates: store.store.location.coordinates
          },
          store: {
            storeName: store.store.storeName,
            logo: store.store.logo,
            description: store.store.description,
            location: store.store.location,
            deliveryRadius: store.store.deliveryRadius || 5000,
            isActive: store.store.isActive !== false, // Default to true if not set
            taxName: store.store.taxName || '',
            taxNumber: store.store.taxNumber || '',
            panNumber: store.store.panNumber || '',
            commission: store.store.commission || 0,
            productStatus: store.store.productStatus || 'Active',
            stockAlertSettings: store.store.stockAlertSettings || {
              outOfStockThreshold: 5,
              lowStockThreshold: 10
            }
          },
          deliveryRadius: store.store.deliveryRadius || 5000,
          isActive: store.store.isActive !== false, // Default to true if not set
          distance: Math.round(distance * 100) / 100, // Round to 2 decimal places
          distanceKm: Math.round((distance / 1000) * 100) / 100, // Convert to km and round
          status: store.status || 'Active',
          createdAt: store.createdAt,
          updatedAt: store.updatedAt
        };
        
        console.log(`Store ${store.store.storeName} is ${storeData.distance}m (${storeData.distanceKm}km) away`);
        return storeData;
      })
      .filter(store => {
        const isWithinRange = store && store.distance <= maxDistance;
        console.log(`Store ${store?._id} within ${maxDistance}m:`, isWithinRange);
        return isWithinRange;
      })
      .sort((a, b) => a.distance - b.distance); // Sort by distance
      
    console.log(`Found ${storesWithDistance.length} stores within ${maxDistance}m`);

    // Apply pagination
    const total = storesWithDistance.length;
    const paginatedStores = storesWithDistance.slice(skip, skip + parseInt(limit));

    // Return the data directly in the format expected by the frontend
    return paginatedStores;
  } catch (error) {
    console.error('Error finding nearby stores:', error);
    throw new ErrorResponse(error.message || 'Error finding nearby stores', error.statusCode || 500);
  }
};

/**
 * Check if a location is within a store's delivery radius
 * @param {Object} store - Store document
 * @param {Array} coordinates - [longitude, latitude]
 * @returns {boolean} - True if within delivery radius
 */
storeSchema.methods.isWithinDeliveryArea = function(coordinates) {
  if (!this.store || !this.store.location || !this.store.deliveryRadius) {
    return false;
  }

  const storeCoords = this.store.location.coordinates;
  const distance = this.constructor.calculateDistance(
    storeCoords[1], storeCoords[0], // lat, lng
    coordinates[1], coordinates[0]  // lat, lng
  );

  return distance <= this.store.deliveryRadius;
};

/**
 * Calculate distance between two points using Haversine formula
 * @param {number} lat1 - Latitude of point 1
 * @param {number} lon1 - Longitude of point 1
 * @param {number} lat2 - Latitude of point 2
 * @param {number} lon2 - Longitude of point 2
 * @returns {number} - Distance in meters
 */
storeSchema.statics.calculateDistance = function(lat1, lon1, lat2, lon2) {
  const R = 6371e3; // Earth's radius in meters
  const φ1 = lat1 * Math.PI / 180; // φ, λ in radians
  const φ2 = lat2 * Math.PI / 180;
  const Δφ = (lat2 - lat1) * Math.PI / 180;
  const Δλ = (lon2 - lon1) * Math.PI / 180;

  const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
            Math.cos(φ1) * Math.cos(φ2) *
            Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  return R * c; // in meters
};

// Add comparePassword method to store schema
storeSchema.methods.comparePassword = async function(enteredPassword) {
  // Get the associated user document
  const user = await mongoose.model('User').findById(this.user).select('+password').exec();
  if (!user) {
    throw new Error('Associated user not found');
  }
  return await bcrypt.compare(enteredPassword, user.password);
};

const Store = mongoose.model('Store', storeSchema);

// Function to handle 2dsphere index creation
const setupGeospatialIndex = async () => {
  try {
    const collection = Store.collection;
    
    // Get all indexes
    const indexes = await collection.indexes();
    
    // Check if we already have a 2dsphere index on store.location
    const hasGeoIndex = indexes.some(index => {
      return index.key && index.key['store.location'] === '2dsphere';
    });

    // If we already have the correct index, we're done
    if (hasGeoIndex) {
      console.log('2dsphere index on store.location already exists');
      return;
    }
    
    // Find and drop any other 2dsphere indexes that might cause conflicts
    const geoIndexes = indexes.filter(index => {
      return Object.values(index.key).some(value => value === '2dsphere');
    });

    for (const index of geoIndexes) {
      try {
        const indexName = index.name;
        if (indexName !== '_id_') { // Don't drop the _id_ index
          console.log(`Dropping conflicting index: ${indexName}`);
          await collection.dropIndex(indexName);
        }
      } catch (err) {
        console.log(`Error dropping index ${index.name}:`, err.message);
      }
    }

    // Ensure documents have the correct structure with proper coordinates order
    await Store.updateMany(
      { 'store.location.coordinates': { $exists: true } },
      [
        {
          $set: {
            'store.location': {
              type: 'Point',
              // Ensure coordinates are in [longitude, latitude] order
              coordinates: {
                $cond: {
                  if: { $eq: [{ $type: "$store.location.coordinates" }, "array"] },
                  then: {
                    $let: {
                      vars: {
                        coords: "$store.location.coordinates"
                      },
                      in: {
                        $cond: {
                          if: { $gt: [{ $arrayElemAt: ["$$coords", 0] }, 90] },
                          then: [
                            { $arrayElemAt: ["$$coords", 0] },
                            { $arrayElemAt: ["$$coords", 1] }
                          ],
                          else: [
                            { $arrayElemAt: ["$$coords", 1] },
                            { $arrayElemAt: ["$$coords", 0] }
                          ]
                        }
                      }
                    }
                  },
                  else: [0, 0] // Default to [0,0] if not an array
                }
              },
              address: '$store.location.address',
              city: '$store.location.city',
              state: '$store.location.state',
              country: '$store.location.country',
              pincode: '$store.location.pincode',
              formattedAddress: '$store.location.formattedAddress'
            }
          }
        }
      ]
    );

    // Create a single 2dsphere index on store.location
    try {
      await collection.createIndex(
        { 'store.location': '2dsphere' },
        { 
          name: 'store_location_2dsphere',
          background: true 
        }
      );
      console.log('Successfully created 2dsphere index on store.location');
    } catch (err) {
      // If the index already exists with a different name, that's fine
      if (err.code === 85) { // IndexOptionsConflict
        console.log('2dsphere index already exists with a different name');
      } else {
        throw err; // Re-throw other errors
      }
    }
  } catch (error) {
    console.error('Error setting up geospatial index:', error);
  }
};

// Run the index setup
setupGeospatialIndex();

module.exports = Store;
