const express = require("express");
const { Op, Sequelize } = require("sequelize");
const { Item, User, Rental, sequelize } = require("../models"); // Import from models/index.js to get models with associations
const { authenticateToken, requireVerifiedEmail, requireAdmin, optionalAuth } = require("../middleware/auth");
const logger = require("../utils/logger");
const { validateS3Keys } = require("../utils/s3KeyValidator");
const { IMAGE_LIMITS } = require("../config/imageLimits");
const router = express.Router();

// Allowed fields for item create/update (prevents mass assignment)
const ALLOWED_ITEM_FIELDS = [
  'name',
  'description',
  'pickUpAvailable',
  'localDeliveryAvailable',
  'localDeliveryRadius',
  'shippingAvailable',
  'inPlaceUseAvailable',
  'pricePerHour',
  'pricePerDay',
  'pricePerWeek',
  'pricePerMonth',
  'replacementCost',
  'address1',
  'address2',
  'city',
  'state',
  'zipCode',
  'country',
  'latitude',
  'longitude',
  'imageFilenames',
  'isAvailable',
  'rules',
  'availableAfter',
  'availableBefore',
  'specifyTimesPerDay',
  'weeklyTimes',
];

/**
 * Extract only allowed fields from request body
 * @param {Object} body - Request body
 * @returns {Object} - Object with only allowed fields
 */
function extractAllowedFields(body) {
  const result = {};
  for (const field of ALLOWED_ITEM_FIELDS) {
    if (body[field] !== undefined) {
      result[field] = body[field];
    }
  }
  return result;
}

router.get("/", async (req, res, next) => {
  try {
    const {
      minPrice,
      maxPrice,
      city,
      zipCode,
      search,
      lat,
      lng,
      radius = 25,
      page = 1,
      limit = 20,
    } = req.query;

    const where = {
      isDeleted: false // Always exclude soft-deleted items from public browse
    };

    if (minPrice || maxPrice) {
      where.pricePerDay = {};
      if (minPrice) where.pricePerDay[Op.gte] = minPrice;
      if (maxPrice) where.pricePerDay[Op.lte] = maxPrice;
    }

    // Location filtering: Radius search OR city/ZIP fallback
    if (lat && lng) {
      // Parse and validate coordinates
      const latNum = parseFloat(lat);
      const lngNum = parseFloat(lng);
      const radiusNum = parseFloat(radius);

      if (!isNaN(latNum) && !isNaN(lngNum) && !isNaN(radiusNum)) {
        // Bounding box pre-filter (fast, uses indexes)
        // ~69 miles per degree latitude, longitude varies by latitude
        const latDelta = radiusNum / 69;
        const lngDelta = radiusNum / (69 * Math.cos(latNum * Math.PI / 180));

        where.latitude = {
          [Op.and]: [
            { [Op.gte]: latNum - latDelta },
            { [Op.lte]: latNum + latDelta },
            { [Op.ne]: null }
          ]
        };
        where.longitude = {
          [Op.and]: [
            { [Op.gte]: lngNum - lngDelta },
            { [Op.lte]: lngNum + lngDelta },
            { [Op.ne]: null }
          ]
        };

        // Haversine formula for exact distance (applied after bounding box)
        // 3959 = Earth's radius in miles
        where[Op.and] = sequelize.literal(`
          (3959 * acos(
            cos(radians(${latNum})) * cos(radians("Item"."latitude")) *
            cos(radians("Item"."longitude") - radians(${lngNum})) +
            sin(radians(${latNum})) * sin(radians("Item"."latitude"))
          )) <= ${radiusNum}
        `);
      }
    } else {
      // Fallback to city/ZIP string matching
      if (city) where.city = { [Op.iLike]: `%${city}%` };
      if (zipCode) where.zipCode = { [Op.iLike]: `%${zipCode}%` };
    }
    if (search) {
      where[Op.or] = [
        { name: { [Op.iLike]: `%${search}%` } },
        { description: { [Op.iLike]: `%${search}%` } },
      ];
    }

    const offset = (page - 1) * limit;

    const { count, rows } = await Item.findAndCountAll({
      where,
      include: [
        {
          model: User,
          as: "owner",
          attributes: ["id", "firstName", "lastName", "imageFilename"],
          where: {
            isBanned: { [Op.ne]: true }
          },
          required: true,
        },
      ],
      limit: parseInt(limit),
      offset: parseInt(offset),
      order: [["createdAt", "DESC"]],
    });

    // Round coordinates to 2 decimal places for map display while keeping precise values in database
    const itemsWithRoundedCoords = rows.map(item => {
      const itemData = item.toJSON();
      if (itemData.latitude !== null && itemData.latitude !== undefined) {
        itemData.latitude = Math.round(parseFloat(itemData.latitude) * 100) / 100;
      }
      if (itemData.longitude !== null && itemData.longitude !== undefined) {
        itemData.longitude = Math.round(parseFloat(itemData.longitude) * 100) / 100;
      }
      return itemData;
    });

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Items search completed", {
      filters: { minPrice, maxPrice, city, zipCode, search, lat, lng, radius },
      resultsCount: count,
      page: parseInt(page),
      limit: parseInt(limit)
    });

    res.json({
      items: itemsWithRoundedCoords,
      totalPages: Math.ceil(count / limit),
      currentPage: parseInt(page),
      totalItems: count,
    });
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Items search failed", {
      error: error.message,
      stack: error.stack,
      query: req.query
    });
    next(error);
  }
});

router.get("/recommendations", authenticateToken, async (req, res, next) => {
  try {
    const userRentals = await Rental.findAll({
      where: { renterId: req.user.id },
      include: [{ model: Item, as: "item" }],
    });

    // For now, just return random available items as recommendations
    const recommendations = await Item.findAll({
      where: {
        isAvailable: true,
        isDeleted: false,
      },
      limit: 10,
      order: [["createdAt", "DESC"]],
    });

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Recommendations fetched", {
      userId: req.user.id,
      recommendationsCount: recommendations.length
    });

    res.json(recommendations);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Recommendations fetch failed", {
      error: error.message,
      stack: error.stack,
      userId: req.user.id
    });
    next(error);
  }
});

// Public endpoint to get reviews for a specific item (must come before /:id route)
router.get('/:id/reviews', async (req, res, next) => {
  try {
    const { Rental, User } = require('../models');

    const reviews = await Rental.findAll({
      where: {
        itemId: req.params.id,
        status: 'completed',
        itemRating: { [Op.not]: null },
        itemReview: { [Op.not]: null },
        itemReviewVisible: true
      },
      include: [
        {
          model: User,
          as: 'renter',
          attributes: ['id', 'firstName', 'lastName', 'imageFilename']
        }
      ],
      order: [['createdAt', 'DESC']]
    });

    const averageRating = reviews.length > 0 
      ? reviews.reduce((sum, review) => sum + review.itemRating, 0) / reviews.length 
      : 0;

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Item reviews fetched", {
      itemId: req.params.id,
      reviewsCount: reviews.length,
      averageRating
    });

    res.json({
      reviews,
      averageRating,
      totalReviews: reviews.length
    });
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Item reviews fetch failed", {
      error: error.message,
      stack: error.stack,
      itemId: req.params.id
    });
    next(error);
  }
});

router.get("/:id", optionalAuth, async (req, res, next) => {
  try {
    const item = await Item.findByPk(req.params.id, {
      include: [
        {
          model: User,
          as: "owner",
          attributes: ["id", "firstName", "lastName", "imageFilename"],
        },
        {
          model: User,
          as: "deleter",
          attributes: ["id", "firstName", "lastName"],
        },
      ],
    });

    if (!item) {
      return res.status(404).json({ error: "Item not found" });
    }

    // Check if item is deleted - only allow admins to view
    if (item.isDeleted) {
      const isAdmin = req.user?.role === 'admin';

      if (!isAdmin) {
        return res.status(404).json({ error: "Item not found" });
      }
    }

    // Round coordinates to 2 decimal places for map display while keeping precise values in database
    const itemResponse = item.toJSON();
    if (itemResponse.latitude !== null && itemResponse.latitude !== undefined) {
      itemResponse.latitude = Math.round(parseFloat(itemResponse.latitude) * 100) / 100;
    }
    if (itemResponse.longitude !== null && itemResponse.longitude !== undefined) {
      itemResponse.longitude = Math.round(parseFloat(itemResponse.longitude) * 100) / 100;
    }

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Item fetched", {
      itemId: req.params.id,
      ownerId: item.ownerId
    });

    res.json(itemResponse);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Item fetch failed", {
      error: error.message,
      stack: error.stack,
      itemId: req.params.id
    });
    next(error);
  }
});

router.post("/", authenticateToken, requireVerifiedEmail, async (req, res, next) => {
  try {
    // Extract only allowed fields (prevents mass assignment)
    const allowedData = extractAllowedFields(req.body);

    // Validate imageFilenames - at least one image is required
    const imageFilenames = Array.isArray(allowedData.imageFilenames)
      ? allowedData.imageFilenames
      : [];

    if (imageFilenames.length === 0) {
      return res.status(400).json({
        error: "At least one image is required to create a listing"
      });
    }

    // Validate required fields
    if (!allowedData.name || !allowedData.name.trim()) {
      return res.status(400).json({ error: "Item name is required" });
    }
    if (!allowedData.address1 || !allowedData.address1.trim()) {
      return res.status(400).json({ error: "Address is required" });
    }
    if (!allowedData.city || !allowedData.city.trim()) {
      return res.status(400).json({ error: "City is required" });
    }
    if (!allowedData.state || !allowedData.state.trim()) {
      return res.status(400).json({ error: "State is required" });
    }
    if (!allowedData.zipCode || !allowedData.zipCode.trim()) {
      return res.status(400).json({ error: "ZIP code is required" });
    }
    if (!allowedData.replacementCost || Number(allowedData.replacementCost) <= 0) {
      return res.status(400).json({ error: "Replacement cost is required" });
    }

    const keyValidation = validateS3Keys(imageFilenames, 'items', { maxKeys: IMAGE_LIMITS.items });
    if (!keyValidation.valid) {
      return res.status(400).json({
        error: keyValidation.error,
        details: keyValidation.invalidKeys
      });
    }
    allowedData.imageFilenames = imageFilenames;

    const item = await Item.create({
      ...allowedData,
      ownerId: req.user.id,
    });

    const itemWithOwner = await Item.findByPk(item.id, {
      include: [
        {
          model: User,
          as: "owner",
          attributes: ["id", "firstName", "lastName", "email", "stripeConnectedAccountId"],
        },
      ],
    });

    // Check if this is the owner's first listing
    const ownerItemCount = await Item.count({
      where: { ownerId: req.user.id }
    });

    // If first listing, send celebration email
    if (ownerItemCount === 1) {
      try {
        const emailServices = require("../services/email");
        await emailServices.userEngagement.sendFirstListingCelebrationEmail(
          itemWithOwner.owner,
          itemWithOwner
        );
        const reqLogger = logger.withRequestId(req.id);
        reqLogger.info("First listing celebration email sent", { ownerId: req.user.id });
      } catch (emailError) {
        // Log but don't fail the item creation
        const reqLogger = logger.withRequestId(req.id);
        reqLogger.error('Failed to send first listing celebration email', {
          error: emailError.message,
          stack: emailError.stack,
          ownerId: req.user.id,
          itemId: item.id
        });
      }
    }

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Item created", {
      itemId: item.id,
      ownerId: req.user.id,
      itemName: req.body.name,
      isFirstListing: ownerItemCount === 1
    });

    res.status(201).json(itemWithOwner);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Item creation failed", {
      error: error.message,
      stack: error.stack,
      ownerId: req.user.id,
      itemData: logger.sanitize(req.body)
    });
    next(error);
  }
});

router.put("/:id", authenticateToken, async (req, res, next) => {
  try {
    const item = await Item.findByPk(req.params.id);

    if (!item) {
      return res.status(404).json({ error: "Item not found" });
    }

    if (item.ownerId !== req.user.id) {
      return res.status(403).json({ error: "Unauthorized" });
    }

    // Extract only allowed fields (prevents mass assignment)
    const allowedData = extractAllowedFields(req.body);

    // Validate imageFilenames if provided
    if (allowedData.imageFilenames !== undefined) {
      const imageFilenames = Array.isArray(allowedData.imageFilenames)
        ? allowedData.imageFilenames
        : [];

      // Require at least one image
      if (imageFilenames.length === 0) {
        return res.status(400).json({
          error: "At least one image is required for a listing"
        });
      }

      const keyValidation = validateS3Keys(imageFilenames, 'items', { maxKeys: IMAGE_LIMITS.items });
      if (!keyValidation.valid) {
        return res.status(400).json({
          error: keyValidation.error,
          details: keyValidation.invalidKeys
        });
      }
      allowedData.imageFilenames = imageFilenames;
    }

    // Validate required fields if they are being updated
    if (allowedData.name !== undefined && (!allowedData.name || !allowedData.name.trim())) {
      return res.status(400).json({ error: "Item name is required" });
    }
    if (allowedData.address1 !== undefined && (!allowedData.address1 || !allowedData.address1.trim())) {
      return res.status(400).json({ error: "Address is required" });
    }
    if (allowedData.city !== undefined && (!allowedData.city || !allowedData.city.trim())) {
      return res.status(400).json({ error: "City is required" });
    }
    if (allowedData.state !== undefined && (!allowedData.state || !allowedData.state.trim())) {
      return res.status(400).json({ error: "State is required" });
    }
    if (allowedData.zipCode !== undefined && (!allowedData.zipCode || !allowedData.zipCode.trim())) {
      return res.status(400).json({ error: "ZIP code is required" });
    }
    if (allowedData.replacementCost !== undefined && (!allowedData.replacementCost || Number(allowedData.replacementCost) <= 0)) {
      return res.status(400).json({ error: "Replacement cost is required" });
    }

    await item.update(allowedData);

    const updatedItem = await Item.findByPk(item.id, {
      include: [
        {
          model: User,
          as: "owner",
          attributes: ["id", "firstName", "lastName"],
        },
      ],
    });

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Item updated", {
      itemId: req.params.id,
      ownerId: req.user.id
    });

    res.json(updatedItem);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Item update failed", {
      error: error.message,
      stack: error.stack,
      itemId: req.params.id,
      ownerId: req.user.id
    });
    next(error);
  }
});

router.delete("/:id", authenticateToken, async (req, res, next) => {
  try {
    const item = await Item.findByPk(req.params.id);

    if (!item) {
      return res.status(404).json({ error: "Item not found" });
    }

    if (item.ownerId !== req.user.id) {
      return res.status(403).json({ error: "Unauthorized" });
    }

    await item.destroy();

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Item deleted", {
      itemId: req.params.id,
      ownerId: req.user.id
    });

    res.status(204).send();
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Item deletion failed", {
      error: error.message,
      stack: error.stack,
      itemId: req.params.id,
      ownerId: req.user.id
    });
    next(error);
  }
});

// Admin endpoints
router.delete("/admin/:id", authenticateToken, requireAdmin, async (req, res, next) => {
  try {
    const { reason } = req.body;

    if (!reason || !reason.trim()) {
      return res.status(400).json({ error: "Deletion reason is required" });
    }

    const item = await Item.findByPk(req.params.id, {
      include: [
        {
          model: User,
          as: "owner",
          attributes: ["id", "firstName", "lastName", "email"],
        },
      ],
    });

    if (!item) {
      return res.status(404).json({ error: "Item not found" });
    }

    if (item.isDeleted) {
      return res.status(400).json({ error: "Item is already deleted" });
    }

    // Check for active or upcoming rentals
    const activeRentals = await Rental.count({
      where: {
        itemId: req.params.id,
        status: {
          [Op.in]: ['pending', 'confirmed', 'active']
        }
      }
    });

    if (activeRentals > 0) {
      return res.status(400).json({
        error: "Cannot delete item with active or upcoming rentals",
        code: "ACTIVE_RENTALS_EXIST",
        activeRentalsCount: activeRentals
      });
    }

    // Soft delete the item
    await item.update({
      isDeleted: true,
      deletedBy: req.user.id,
      deletedAt: new Date(),
      deletionReason: reason.trim()
    });

    const updatedItem = await Item.findByPk(item.id, {
      include: [
        {
          model: User,
          as: "owner",
          attributes: ["id", "firstName", "lastName"],
        },
        {
          model: User,
          as: "deleter",
          attributes: ["id", "firstName", "lastName"],
        }
      ],
    });

    // Send email notification to owner
    try {
      const emailServices = require("../services/email");
      await emailServices.userEngagement.sendItemDeletionNotificationToOwner(
        item.owner,
        item,
        reason.trim()
      );
      logger.info("Item deletion notification email sent", { ownerId: item.ownerId, itemId: item.id });
    } catch (emailError) {
      // Log but don't fail the deletion
      logger.error('Failed to send item deletion notification email', {
        error: emailError.message,
        stack: emailError.stack,
        ownerId: item.ownerId,
        itemId: item.id
      });
    }

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Item soft deleted by admin", {
      itemId: req.params.id,
      deletedBy: req.user.id,
      ownerId: item.ownerId,
      reason: reason.trim()
    });

    res.json(updatedItem);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Admin item soft delete failed", {
      error: error.message,
      stack: error.stack,
      itemId: req.params.id,
      adminId: req.user.id
    });
    next(error);
  }
});

router.patch("/admin/:id/restore", authenticateToken, requireAdmin, async (req, res, next) => {
  try {
    const item = await Item.findByPk(req.params.id);

    if (!item) {
      return res.status(404).json({ error: "Item not found" });
    }

    if (!item.isDeleted) {
      return res.status(400).json({ error: "Item is not deleted" });
    }

    // Restore the item
    await item.update({
      isDeleted: false,
      deletedBy: null,
      deletedAt: null,
      deletionReason: null
    });

    const updatedItem = await Item.findByPk(item.id, {
      include: [
        {
          model: User,
          as: "owner",
          attributes: ["id", "firstName", "lastName"],
        }
      ],
    });

    const reqLogger = logger.withRequestId(req.id);
    reqLogger.info("Item restored by admin", {
      itemId: req.params.id,
      restoredBy: req.user.id,
      ownerId: item.ownerId
    });

    res.json(updatedItem);
  } catch (error) {
    const reqLogger = logger.withRequestId(req.id);
    reqLogger.error("Admin item restore failed", {
      error: error.message,
      stack: error.stack,
      itemId: req.params.id,
      adminId: req.user.id
    });
    next(error);
  }
});

module.exports = router;
