const request = require('supertest');
const express = require('express');

// Mock Sequelize operators
jest.mock('sequelize', () => ({
  Op: {
    gte: 'gte',
    lte: 'lte',
    iLike: 'iLike',
    or: 'or',
    not: 'not'
  }
}));

// Mock models - define mocks inline to avoid hoisting issues
jest.mock('../../../models', () => ({
  Item: {
    findAndCountAll: jest.fn(),
    findByPk: jest.fn(),
    create: jest.fn(),
    findAll: jest.fn(),
    count: jest.fn()
  },
  User: jest.fn(),
  Rental: {
    findAll: jest.fn(),
    count: jest.fn()
  }
}));

// Mock auth middleware
jest.mock('../../../middleware/auth', () => ({
  authenticateToken: (req, res, next) => {
    if (req.headers.authorization) {
      req.user = { id: 1 };
      next();
    } else {
      res.status(401).json({ error: 'No token provided' });
    }
  },
  requireVerifiedEmail: (req, res, next) => next(),
  requireAdmin: (req, res, next) => next(),
  optionalAuth: (req, res, next) => next()
}));

// Mock logger
jest.mock('../../../utils/logger', () => ({
  info: jest.fn(),
  error: jest.fn(),
  warn: jest.fn(),
  withRequestId: jest.fn(() => ({
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn()
  })),
  sanitize: jest.fn(data => data)
}));

// Mock email services
jest.mock('../../../services/email', () => ({
  userEngagement: {
    sendFirstListingCelebrationEmail: jest.fn().mockResolvedValue(true),
    sendItemDeletionNotificationToOwner: jest.fn().mockResolvedValue(true)
  }
}));

const { Item, User, Rental } = require('../../../models');
const { Op } = require('sequelize');
const itemsRoutes = require('../../../routes/items');

// Get references to the mock functions after importing
const mockItemFindAndCountAll = Item.findAndCountAll;
const mockItemFindByPk = Item.findByPk;
const mockItemCreate = Item.create;
const mockItemFindAll = Item.findAll;
const mockItemCount = Item.count;
const mockRentalFindAll = Rental.findAll;
const mockUserModel = User;

// Set up Express app for testing
const app = express();
app.use(express.json());
app.use('/items', itemsRoutes);

// Error handler middleware
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message });
});

describe('Items Routes', () => {
  let consoleSpy;

  beforeEach(() => {
    jest.clearAllMocks();
    consoleSpy = jest.spyOn(console, 'log').mockImplementation();
    jest.spyOn(console, 'error').mockImplementation();
    mockItemCount.mockResolvedValue(1); // Default to not first listing
  });

  afterEach(() => {
    consoleSpy.mockRestore();
  });

  describe('GET /', () => {
    const mockItems = [
      {
        id: 1,
        name: 'Camping Tent',
        description: 'Great for camping',
        pricePerDay: 25.99,
        city: 'New York',
        zipCode: '10001',
        latitude: 40.7484405,
        longitude: -73.9856644,
        createdAt: new Date('2023-01-01'),
        toJSON: () => ({
          id: 1,
          name: 'Camping Tent',
          description: 'Great for camping',
          pricePerDay: 25.99,
          city: 'New York',
          zipCode: '10001',
          latitude: 40.7484405,
          longitude: -73.9856644,
          createdAt: new Date('2023-01-01'),
          owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' }
        })
      },
      {
        id: 2,
        name: 'Mountain Bike',
        description: 'Perfect for trails',
        pricePerDay: 35.50,
        city: 'Los Angeles',
        zipCode: '90210',
        latitude: 34.0522265,
        longitude: -118.2436596,
        createdAt: new Date('2023-01-02'),
        toJSON: () => ({
          id: 2,
          name: 'Mountain Bike',
          description: 'Perfect for trails',
          pricePerDay: 35.50,
          city: 'Los Angeles',
          zipCode: '90210',
          latitude: 34.0522265,
          longitude: -118.2436596,
          createdAt: new Date('2023-01-02'),
          owner: { id: 3, username: 'owner2', firstName: 'Jane', lastName: 'Smith' }
        })
      }
    ];

    it('should get all items with default pagination', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 2,
        rows: mockItems
      });

      const response = await request(app)
        .get('/items');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        items: [
          {
            id: 1,
            name: 'Camping Tent',
            description: 'Great for camping',
            pricePerDay: 25.99,
            city: 'New York',
            zipCode: '10001',
            latitude: 40.75, // Rounded to 2 decimal places
            longitude: -73.99, // Rounded to 2 decimal places
            createdAt: mockItems[0].createdAt.toISOString(),
            owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' }
          },
          {
            id: 2,
            name: 'Mountain Bike',
            description: 'Perfect for trails',
            pricePerDay: 35.50,
            city: 'Los Angeles',
            zipCode: '90210',
            latitude: 34.05, // Rounded to 2 decimal places
            longitude: -118.24, // Rounded to 2 decimal places
            createdAt: mockItems[1].createdAt.toISOString(),
            owner: { id: 3, username: 'owner2', firstName: 'Jane', lastName: 'Smith' }
          }
        ],
        totalPages: 1,
        currentPage: 1,
        totalItems: 2
      });

      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: { isDeleted: false },
        include: [
          {
            model: mockUserModel,
            as: 'owner',
            attributes: ['id', 'firstName', 'lastName', 'imageFilename']
          }
        ],
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should handle custom pagination', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 50,
        rows: mockItems
      });

      const response = await request(app)
        .get('/items?page=3&limit=10');

      expect(response.status).toBe(200);
      expect(response.body.totalPages).toBe(5);
      expect(response.body.currentPage).toBe(3);
      expect(response.body.totalItems).toBe(50);

      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: { isDeleted: false },
        include: expect.any(Array),
        limit: 10,
        offset: 20, // (page 3 - 1) * limit 10
        order: [['createdAt', 'DESC']]
      });
    });

    it('should filter by price range', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [mockItems[0]]
      });

      const response = await request(app)
        .get('/items?minPrice=20&maxPrice=30');

      expect(response.status).toBe(200);
      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: {
          isDeleted: false,
          pricePerDay: {
            gte: '20',
            lte: '30'
          }
        },
        include: expect.any(Array),
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should filter by minimum price only', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [mockItems[1]]
      });

      const response = await request(app)
        .get('/items?minPrice=30');

      expect(response.status).toBe(200);
      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: {
          isDeleted: false,
          pricePerDay: {
            gte: '30'
          }
        },
        include: expect.any(Array),
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should filter by maximum price only', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [mockItems[0]]
      });

      const response = await request(app)
        .get('/items?maxPrice=30');

      expect(response.status).toBe(200);
      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: {
          isDeleted: false,
          pricePerDay: {
            lte: '30'
          }
        },
        include: expect.any(Array),
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should filter by city', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [mockItems[0]]
      });

      const response = await request(app)
        .get('/items?city=New York');

      expect(response.status).toBe(200);
      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: {
          isDeleted: false,
          city: { iLike: '%New York%' }
        },
        include: expect.any(Array),
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should filter by zip code', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [mockItems[0]]
      });

      const response = await request(app)
        .get('/items?zipCode=10001');

      expect(response.status).toBe(200);
      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: {
          isDeleted: false,
          zipCode: { iLike: '%10001%' }
        },
        include: expect.any(Array),
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should search by name and description', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [mockItems[0]]
      });

      const response = await request(app)
        .get('/items?search=camping');

      expect(response.status).toBe(200);
      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: {
          isDeleted: false,
          or: [
            { name: { iLike: '%camping%' } },
            { description: { iLike: '%camping%' } }
          ]
        },
        include: expect.any(Array),
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should combine multiple filters', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [mockItems[0]]
      });

      const response = await request(app)
        .get('/items?search=tent&city=New&minPrice=20&maxPrice=30');

      expect(response.status).toBe(200);
      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: {
          isDeleted: false,
          pricePerDay: {
            gte: '20',
            lte: '30'
          },
          city: { iLike: '%New%' },
          or: [
            { name: { iLike: '%tent%' } },
            { description: { iLike: '%tent%' } }
          ]
        },
        include: expect.any(Array),
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should handle coordinates rounding with null values', async () => {
      const itemWithNullCoords = {
        id: 3,
        name: 'Test Item',
        latitude: null,
        longitude: null,
        toJSON: () => ({
          id: 3,
          name: 'Test Item',
          latitude: null,
          longitude: null
        })
      };

      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [itemWithNullCoords]
      });

      const response = await request(app)
        .get('/items');

      expect(response.status).toBe(200);
      expect(response.body.items[0].latitude).toBeNull();
      expect(response.body.items[0].longitude).toBeNull();
    });

    it('should handle database errors', async () => {
      const dbError = new Error('Database connection failed');
      mockItemFindAndCountAll.mockRejectedValue(dbError);

      const response = await request(app)
        .get('/items');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database connection failed' });
    });

    it('should return empty results when no items found', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 0,
        rows: []
      });

      const response = await request(app)
        .get('/items');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        items: [],
        totalPages: 0,
        currentPage: 1,
        totalItems: 0
      });
    });
  });

  describe('GET /recommendations', () => {
    const mockRecommendations = [
      { id: 1, name: 'Item 1', isAvailable: true },
      { id: 2, name: 'Item 2', isAvailable: true }
    ];

    it('should get recommendations for authenticated user', async () => {
      mockRentalFindAll.mockResolvedValue([]);
      mockItemFindAll.mockResolvedValue(mockRecommendations);

      const response = await request(app)
        .get('/items/recommendations')
        .set('Authorization', 'Bearer valid_token');

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockRecommendations);

      expect(mockRentalFindAll).toHaveBeenCalledWith({
        where: { renterId: 1 },
        include: [{ model: Item, as: 'item' }]
      });

      expect(mockItemFindAll).toHaveBeenCalledWith({
        where: { isAvailable: true, isDeleted: false },
        limit: 10,
        order: [['createdAt', 'DESC']]
      });
    });

    it('should require authentication', async () => {
      const response = await request(app)
        .get('/items/recommendations');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'No token provided' });
    });

    it('should handle database errors', async () => {
      const dbError = new Error('Database error');
      mockRentalFindAll.mockRejectedValue(dbError);

      const response = await request(app)
        .get('/items/recommendations')
        .set('Authorization', 'Bearer valid_token');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database error' });
    });
  });

  describe('GET /:id/reviews', () => {
    const mockReviews = [
      {
        id: 1,
        itemId: 1,
        itemRating: 5,
        itemReview: 'Great item!',
        itemReviewVisible: true,
        status: 'completed',
        createdAt: new Date('2023-01-01'),
        renter: { id: 1, firstName: 'John', lastName: 'Doe' }
      },
      {
        id: 2,
        itemId: 1,
        itemRating: 4,
        itemReview: 'Good quality',
        itemReviewVisible: true,
        status: 'completed',
        createdAt: new Date('2023-01-02'),
        renter: { id: 2, firstName: 'Jane', lastName: 'Smith' }
      }
    ];

    it('should get reviews for a specific item', async () => {
      mockRentalFindAll.mockResolvedValue(mockReviews);

      const response = await request(app)
        .get('/items/1/reviews');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        reviews: [
          {
            id: 1,
            itemId: 1,
            itemRating: 5,
            itemReview: 'Great item!',
            itemReviewVisible: true,
            status: 'completed',
            createdAt: '2023-01-01T00:00:00.000Z',
            renter: { id: 1, firstName: 'John', lastName: 'Doe' }
          },
          {
            id: 2,
            itemId: 1,
            itemRating: 4,
            itemReview: 'Good quality',
            itemReviewVisible: true,
            status: 'completed',
            createdAt: '2023-01-02T00:00:00.000Z',
            renter: { id: 2, firstName: 'Jane', lastName: 'Smith' }
          }
        ],
        averageRating: 4.5,
        totalReviews: 2
      });

      expect(mockRentalFindAll).toHaveBeenCalledWith({
        where: {
          itemId: '1',
          status: 'completed',
          itemRating: { not: null },
          itemReview: { not: null },
          itemReviewVisible: true
        },
        include: [
          {
            model: mockUserModel,
            as: 'renter',
            attributes: ['id', 'firstName', 'lastName', 'imageFilename']
          }
        ],
        order: [['createdAt', 'DESC']]
      });
    });

    it('should handle no reviews found', async () => {
      mockRentalFindAll.mockResolvedValue([]);

      const response = await request(app)
        .get('/items/1/reviews');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        reviews: [],
        averageRating: 0,
        totalReviews: 0
      });
    });

    it('should handle database errors', async () => {
      const dbError = new Error('Database error');
      mockRentalFindAll.mockRejectedValue(dbError);

      const response = await request(app)
        .get('/items/1/reviews');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database error' });
    });
  });

  describe('GET /:id', () => {
    const mockItem = {
      id: 1,
      name: 'Test Item',
      latitude: 40.7484405,
      longitude: -73.9856644,
      toJSON: () => ({
        id: 1,
        name: 'Test Item',
        latitude: 40.7484405,
        longitude: -73.9856644,
        owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' }
      })
    };

    it('should get a specific item by ID', async () => {
      mockItemFindByPk.mockResolvedValue(mockItem);

      const response = await request(app)
        .get('/items/1');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        id: 1,
        name: 'Test Item',
        latitude: 40.75, // Rounded
        longitude: -73.99, // Rounded
        owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' }
      });

      expect(mockItemFindByPk).toHaveBeenCalledWith('1', {
        include: [
          {
            model: mockUserModel,
            as: 'owner',
            attributes: ['id', 'firstName', 'lastName', 'imageFilename']
          },
          {
            model: mockUserModel,
            as: 'deleter',
            attributes: ['id', 'firstName', 'lastName']
          }
        ]
      });
    });

    it('should return 404 for non-existent item', async () => {
      mockItemFindByPk.mockResolvedValue(null);

      const response = await request(app)
        .get('/items/999');

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: 'Item not found' });
    });

    it('should handle database errors', async () => {
      const dbError = new Error('Database error');
      mockItemFindByPk.mockRejectedValue(dbError);

      const response = await request(app)
        .get('/items/1');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database error' });
    });
  });

  describe('POST /', () => {
    const validUuid = '550e8400-e29b-41d4-a716-446655440000';
    const newItemData = {
      name: 'New Item',
      description: 'A new test item',
      pricePerDay: 25.99,
      address1: '123 Test St',
      city: 'Test City',
      state: 'TS',
      zipCode: '12345',
      replacementCost: 100,
      imageFilenames: [`items/${validUuid}.jpg`]
    };

    const mockCreatedItem = {
      id: 1,
      ...newItemData,
      ownerId: 1
    };

    const mockItemWithOwner = {
      id: 1,
      ...newItemData,
      ownerId: 1,
      owner: { id: 1, username: 'user1', firstName: 'John', lastName: 'Doe' }
    };

    it('should create a new item', async () => {
      mockItemCreate.mockResolvedValue(mockCreatedItem);
      mockItemFindByPk.mockResolvedValue(mockItemWithOwner);

      const response = await request(app)
        .post('/items')
        .set('Authorization', 'Bearer valid_token')
        .send(newItemData);

      expect(response.status).toBe(201);
      expect(response.body).toEqual(mockItemWithOwner);

      expect(mockItemCreate).toHaveBeenCalledWith({
        ...newItemData,
        ownerId: 1
      });

      expect(mockItemFindByPk).toHaveBeenCalledWith(1, {
        include: [
          {
            model: mockUserModel,
            as: 'owner',
            attributes: ['id', 'firstName', 'lastName', 'email', 'stripeConnectedAccountId']
          }
        ]
      });
    });

    it('should require authentication', async () => {
      const response = await request(app)
        .post('/items')
        .send(newItemData);

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'No token provided' });
    });

    it('should handle database errors during creation', async () => {
      const dbError = new Error('Database error');
      mockItemCreate.mockRejectedValue(dbError);

      const response = await request(app)
        .post('/items')
        .set('Authorization', 'Bearer valid_token')
        .send(newItemData);

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database error' });
    });

    it('should handle errors during owner fetch', async () => {
      mockItemCreate.mockResolvedValue(mockCreatedItem);
      const dbError = new Error('Database error');
      mockItemFindByPk.mockRejectedValue(dbError);

      const response = await request(app)
        .post('/items')
        .set('Authorization', 'Bearer valid_token')
        .send(newItemData);

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database error' });
    });
  });

  describe('PUT /:id', () => {
    const updateData = {
      name: 'Updated Item',
      description: 'Updated description',
      pricePerDay: 35.99
    };

    const mockItem = {
      id: 1,
      name: 'Original Item',
      ownerId: 1,
      update: jest.fn()
    };

    const mockUpdatedItem = {
      id: 1,
      ...updateData,
      ownerId: 1,
      owner: { id: 1, username: 'user1', firstName: 'John', lastName: 'Doe' }
    };

    beforeEach(() => {
      mockItem.update.mockReset();
    });

    it('should update an item when user is owner', async () => {
      mockItemFindByPk
        .mockResolvedValueOnce(mockItem) // First call for authorization check
        .mockResolvedValueOnce(mockUpdatedItem); // Second call for returning updated item
      mockItem.update.mockResolvedValue();

      const response = await request(app)
        .put('/items/1')
        .set('Authorization', 'Bearer valid_token')
        .send(updateData);

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockUpdatedItem);

      expect(mockItem.update).toHaveBeenCalledWith(updateData);
    });

    it('should return 404 for non-existent item', async () => {
      mockItemFindByPk.mockResolvedValue(null);

      const response = await request(app)
        .put('/items/999')
        .set('Authorization', 'Bearer valid_token')
        .send(updateData);

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: 'Item not found' });
    });

    it('should return 403 when user is not owner', async () => {
      const itemOwnedByOther = { ...mockItem, ownerId: 2 };
      mockItemFindByPk.mockResolvedValue(itemOwnedByOther);

      const response = await request(app)
        .put('/items/1')
        .set('Authorization', 'Bearer valid_token')
        .send(updateData);

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Unauthorized' });
    });

    it('should require authentication', async () => {
      const response = await request(app)
        .put('/items/1')
        .send(updateData);

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'No token provided' });
    });

    it('should handle database errors', async () => {
      const dbError = new Error('Database error');
      mockItemFindByPk.mockRejectedValue(dbError);

      const response = await request(app)
        .put('/items/1')
        .set('Authorization', 'Bearer valid_token')
        .send(updateData);

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database error' });
    });

    it('should handle update errors', async () => {
      const updateError = new Error('Update failed');
      mockItemFindByPk.mockResolvedValue(mockItem);
      mockItem.update.mockRejectedValue(updateError);

      const response = await request(app)
        .put('/items/1')
        .set('Authorization', 'Bearer valid_token')
        .send(updateData);

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Update failed' });
    });
  });

  describe('DELETE /:id', () => {
    const mockItem = {
      id: 1,
      name: 'Item to delete',
      ownerId: 1,
      destroy: jest.fn()
    };

    beforeEach(() => {
      mockItem.destroy.mockReset();
    });

    it('should delete an item when user is owner', async () => {
      mockItemFindByPk.mockResolvedValue(mockItem);
      mockItem.destroy.mockResolvedValue();

      const response = await request(app)
        .delete('/items/1')
        .set('Authorization', 'Bearer valid_token');

      expect(response.status).toBe(204);
      expect(response.body).toEqual({});
      expect(mockItem.destroy).toHaveBeenCalled();
    });

    it('should return 404 for non-existent item', async () => {
      mockItemFindByPk.mockResolvedValue(null);

      const response = await request(app)
        .delete('/items/999')
        .set('Authorization', 'Bearer valid_token');

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: 'Item not found' });
    });

    it('should return 403 when user is not owner', async () => {
      const itemOwnedByOther = { ...mockItem, ownerId: 2 };
      mockItemFindByPk.mockResolvedValue(itemOwnedByOther);

      const response = await request(app)
        .delete('/items/1')
        .set('Authorization', 'Bearer valid_token');

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Unauthorized' });
    });

    it('should require authentication', async () => {
      const response = await request(app)
        .delete('/items/1');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'No token provided' });
    });

    it('should handle database errors', async () => {
      const dbError = new Error('Database error');
      mockItemFindByPk.mockRejectedValue(dbError);

      const response = await request(app)
        .delete('/items/1')
        .set('Authorization', 'Bearer valid_token');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database error' });
    });

    it('should handle deletion errors', async () => {
      const deleteError = new Error('Delete failed');
      mockItemFindByPk.mockResolvedValue(mockItem);
      mockItem.destroy.mockRejectedValue(deleteError);

      const response = await request(app)
        .delete('/items/1')
        .set('Authorization', 'Bearer valid_token');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Delete failed' });
    });
  });

  describe('Coordinate Rounding', () => {
    it('should round coordinates to 2 decimal places', async () => {
      const itemWithPreciseCoords = {
        toJSON: () => ({
          id: 1,
          latitude: 40.748440123456,
          longitude: -73.985664789012
        })
      };

      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [itemWithPreciseCoords]
      });

      const response = await request(app)
        .get('/items');

      expect(response.status).toBe(200);
      expect(response.body.items[0].latitude).toBe(40.75);
      expect(response.body.items[0].longitude).toBe(-73.99);
    });

    it('should handle undefined coordinates', async () => {
      const itemWithUndefinedCoords = {
        toJSON: () => ({
          id: 1,
          latitude: undefined,
          longitude: undefined
        })
      };

      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [itemWithUndefinedCoords]
      });

      const response = await request(app)
        .get('/items');

      expect(response.status).toBe(200);
      expect(response.body.items[0].latitude).toBeUndefined();
      expect(response.body.items[0].longitude).toBeUndefined();
    });

    it('should handle zero coordinates', async () => {
      const itemWithZeroCoords = {
        toJSON: () => ({
          id: 1,
          latitude: 0,
          longitude: 0
        })
      };

      mockItemFindAndCountAll.mockResolvedValue({
        count: 1,
        rows: [itemWithZeroCoords]
      });

      const response = await request(app)
        .get('/items');

      expect(response.status).toBe(200);
      expect(response.body.items[0].latitude).toBe(0);
      expect(response.body.items[0].longitude).toBe(0);
    });
  });

  describe('Edge Cases', () => {
    it('should handle very large page numbers', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 0,
        rows: []
      });

      const response = await request(app)
        .get('/items?page=999999&limit=10');

      expect(response.status).toBe(200);
      expect(response.body.currentPage).toBe(999999);
      expect(response.body.totalPages).toBe(0);
    });

    it('should handle invalid query parameters gracefully', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 0,
        rows: []
      });

      const response = await request(app)
        .get('/items?page=invalid&limit=abc&minPrice=notanumber');

      expect(response.status).toBe(200);
      // Express will handle invalid numbers, typically converting to NaN or 0
    });

    it('should handle empty search queries', async () => {
      mockItemFindAndCountAll.mockResolvedValue({
        count: 0,
        rows: []
      });

      const response = await request(app)
        .get('/items?search=');

      expect(response.status).toBe(200);
      expect(mockItemFindAndCountAll).toHaveBeenCalledWith({
        where: { isDeleted: false },
        include: expect.any(Array),
        limit: 20,
        offset: 0,
        order: [['createdAt', 'DESC']]
      });
    });
  });

  describe('Image Handling', () => {
    const validUuid1 = '550e8400-e29b-41d4-a716-446655440000';
    const validUuid2 = '660e8400-e29b-41d4-a716-446655440001';

    describe('POST / with imageFilenames', () => {
      const newItemWithImages = {
        name: 'New Item',
        description: 'A new test item',
        pricePerDay: 25.99,
        address1: '123 Test St',
        city: 'Test City',
        state: 'TS',
        zipCode: '12345',
        replacementCost: 100,
        imageFilenames: [
          `items/${validUuid1}.jpg`,
          `items/${validUuid2}.png`
        ]
      };

      const mockCreatedItem = {
        id: 1,
        name: 'New Item',
        description: 'A new test item',
        pricePerDay: 25.99,
        imageFilenames: newItemWithImages.imageFilenames,
        ownerId: 1
      };

      it('should create item with valid imageFilenames', async () => {
        mockItemCreate.mockResolvedValue(mockCreatedItem);
        mockItemFindByPk.mockResolvedValue(mockCreatedItem);

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(newItemWithImages);

        expect(response.status).toBe(201);
        expect(mockItemCreate).toHaveBeenCalledWith(
          expect.objectContaining({
            imageFilenames: newItemWithImages.imageFilenames,
            ownerId: 1
          })
        );
      });

      it('should reject item without imageFilenames', async () => {
        const itemWithoutImages = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100
        };

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithoutImages);

        expect(response.status).toBe(400);
        expect(response.body.error).toContain('At least one image is required');
      });

      it('should reject invalid S3 key format', async () => {
        const itemWithInvalidKey = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100,
          imageFilenames: ['invalid-key.jpg']
        };

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithInvalidKey);

        expect(response.status).toBe(400);
        expect(response.body.error).toBeDefined();
      });

      it('should reject keys with wrong folder prefix', async () => {
        const itemWithWrongFolder = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100,
          imageFilenames: [`profiles/${validUuid1}.jpg`]
        };

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithWrongFolder);

        expect(response.status).toBe(400);
      });

      it('should reject exceeding max images (10)', async () => {
        const tooManyImages = Array(11).fill(0).map((_, i) =>
          `items/550e8400-e29b-41d4-a716-44665544${String(i).padStart(4, '0')}.jpg`
        );

        const itemWithTooManyImages = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100,
          imageFilenames: tooManyImages
        };

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithTooManyImages);

        expect(response.status).toBe(400);
        expect(response.body.error).toContain('Maximum');
      });

      it('should accept exactly 10 images', async () => {
        const maxImages = Array(10).fill(0).map((_, i) =>
          `items/550e8400-e29b-41d4-a716-44665544${String(i).padStart(4, '0')}.jpg`
        );

        const itemWithMaxImages = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100,
          imageFilenames: maxImages
        };

        mockItemCreate.mockResolvedValue({ ...mockCreatedItem, imageFilenames: maxImages });
        mockItemFindByPk.mockResolvedValue({ ...mockCreatedItem, imageFilenames: maxImages });

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithMaxImages);

        expect(response.status).toBe(201);
      });

      it('should reject duplicate image keys', async () => {
        const itemWithDuplicates = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100,
          imageFilenames: [
            `items/${validUuid1}.jpg`,
            `items/${validUuid1}.jpg`
          ]
        };

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithDuplicates);

        expect(response.status).toBe(400);
        expect(response.body.error).toContain('Duplicate');
      });

      it('should reject path traversal attempts', async () => {
        const itemWithPathTraversal = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100,
          imageFilenames: [`../items/${validUuid1}.jpg`]
        };

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithPathTraversal);

        expect(response.status).toBe(400);
      });

      it('should reject non-image extensions', async () => {
        const itemWithNonImage = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100,
          imageFilenames: [`items/${validUuid1}.exe`]
        };

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithNonImage);

        expect(response.status).toBe(400);
      });

      it('should reject empty imageFilenames array', async () => {
        const itemWithEmptyImages = {
          name: 'New Item',
          description: 'A new test item',
          pricePerDay: 25.99,
          address1: '123 Test St',
          city: 'Test City',
          state: 'TS',
          zipCode: '12345',
          replacementCost: 100,
          imageFilenames: []
        };

        const response = await request(app)
          .post('/items')
          .set('Authorization', 'Bearer valid_token')
          .send(itemWithEmptyImages);

        expect(response.status).toBe(400);
        expect(response.body.error).toContain('At least one image is required');
      });
    });

    describe('PUT /:id with imageFilenames', () => {
      const mockItem = {
        id: 1,
        name: 'Original Item',
        ownerId: 1,
        imageFilenames: [`items/${validUuid1}.jpg`],
        update: jest.fn()
      };

      const mockUpdatedItem = {
        id: 1,
        name: 'Updated Item',
        ownerId: 1,
        imageFilenames: [`items/${validUuid2}.png`],
        owner: { id: 1, firstName: 'John', lastName: 'Doe' }
      };

      beforeEach(() => {
        mockItem.update.mockReset();
      });

      it('should update item with new imageFilenames', async () => {
        mockItemFindByPk
          .mockResolvedValueOnce(mockItem)
          .mockResolvedValueOnce(mockUpdatedItem);
        mockItem.update.mockResolvedValue();

        const response = await request(app)
          .put('/items/1')
          .set('Authorization', 'Bearer valid_token')
          .send({
            name: 'Updated Item',
            imageFilenames: [`items/${validUuid2}.png`]
          });

        expect(response.status).toBe(200);
        expect(mockItem.update).toHaveBeenCalledWith(
          expect.objectContaining({
            imageFilenames: [`items/${validUuid2}.png`]
          })
        );
      });

      it('should reject invalid imageFilenames on update', async () => {
        mockItemFindByPk.mockResolvedValue(mockItem);

        const response = await request(app)
          .put('/items/1')
          .set('Authorization', 'Bearer valid_token')
          .send({
            imageFilenames: ['invalid-key.jpg']
          });

        expect(response.status).toBe(400);
      });

      it('should reject clearing imageFilenames with empty array', async () => {
        mockItemFindByPk.mockResolvedValueOnce(mockItem);

        const response = await request(app)
          .put('/items/1')
          .set('Authorization', 'Bearer valid_token')
          .send({
            imageFilenames: []
          });

        expect(response.status).toBe(400);
        expect(response.body.error).toContain('At least one image is required');
      });
    });
  });
});