const request = require('supertest');
const express = require('express');
const rentalsRouter = require('../../../routes/rentals');

// Mock all dependencies
jest.mock('../../../models', () => ({
  Rental: {
    findAll: jest.fn(),
    findByPk: jest.fn(),
    findOne: jest.fn(),
    create: jest.fn(),
  },
  Item: {
    findByPk: jest.fn(),
  },
  User: {
    findByPk: jest.fn(),
  },
}));

jest.mock('../../../middleware/auth', () => ({
  authenticateToken: jest.fn((req, res, next) => {
    req.user = { id: 1 };
    next();
  }),
  requireVerifiedEmail: jest.fn((req, res, next) => next()),
}));

jest.mock('../../../utils/rentalDurationCalculator', () => ({
  calculateRentalCost: jest.fn(() => 100),
}));

jest.mock('../../../services/email', () => ({
  rentalFlow: {
    sendRentalRequestEmail: jest.fn(),
    sendRentalRequestConfirmationEmail: jest.fn(),
    sendRentalApprovalConfirmationEmail: jest.fn(),
    sendRentalConfirmation: jest.fn(),
    sendRentalDeclinedEmail: jest.fn(),
    sendRentalCompletedEmail: jest.fn(),
    sendRentalCancelledEmail: jest.fn(),
    sendDamageReportEmail: jest.fn(),
    sendLateReturnNotificationEmail: jest.fn(),
    sendRentalCompletionEmails: jest.fn().mockResolvedValue(),
    sendRentalCancellationEmails: jest.fn().mockResolvedValue(),
    sendAuthenticationRequiredEmail: jest.fn().mockResolvedValue(),
  },
  rentalReminder: {
    sendUpcomingRentalReminder: jest.fn(),
  },
  customerService: {
    sendLostItemToCustomerService: jest.fn().mockResolvedValue(),
  },
  payment: {
    sendPaymentDeclinedNotification: jest.fn().mockResolvedValue(),
    sendPaymentMethodUpdatedNotification: jest.fn().mockResolvedValue(),
  },
}));

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

jest.mock('../../../services/lateReturnService', () => ({
  calculateLateFee: jest.fn(),
  processLateReturn: jest.fn(),
}));

jest.mock('../../../services/damageAssessmentService', () => ({
  assessDamage: jest.fn(),
  processDamageFee: jest.fn(),
  processDamageAssessment: jest.fn(),
}));

jest.mock('../../../utils/feeCalculator', () => ({
  calculateRentalFees: jest.fn(() => ({
    totalChargedAmount: 120,
    platformFee: 20,
    payoutAmount: 100,
  })),
  formatFeesForDisplay: jest.fn(() => ({
    baseAmount: '$100.00',
    platformFee: '$20.00',
    totalAmount: '$120.00',
  })),
}));

jest.mock('../../../services/refundService', () => ({
  getRefundPreview: jest.fn(),
  processCancellation: jest.fn(),
}));

jest.mock('../../../services/stripeService', () => ({
  chargePaymentMethod: jest.fn(),
}));

jest.mock('../../../services/stripeWebhookService', () => ({
  reconcilePayoutStatuses: jest.fn().mockResolvedValue(),
}));

jest.mock('../../../services/payoutService', () => ({
  triggerPayoutOnCompletion: jest.fn().mockResolvedValue(),
  processRentalPayout: jest.fn().mockResolvedValue(),
}));

jest.mock('../../../services/eventBridgeSchedulerService', () => ({
  createConditionCheckSchedules: jest.fn().mockResolvedValue(),
}));

// Mock stripe module
jest.mock('stripe', () => {
  return jest.fn().mockImplementation(() => ({
    paymentIntents: {
      retrieve: jest.fn(),
    },
  }));
});

const { Rental, Item, User } = require('../../../models');
const FeeCalculator = require('../../../utils/feeCalculator');
const RentalDurationCalculator = require('../../../utils/rentalDurationCalculator');
const RefundService = require('../../../services/refundService');
const StripeService = require('../../../services/stripeService');
const PayoutService = require('../../../services/payoutService');
const DamageAssessmentService = require('../../../services/damageAssessmentService');
const EventBridgeSchedulerService = require('../../../services/eventBridgeSchedulerService');
const stripe = require('stripe');

// Create express app with the router
const app = express();
app.use(express.json());
app.use('/rentals', rentalsRouter);

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

// Mock models
const mockRentalFindAll = Rental.findAll;
const mockRentalFindByPk = Rental.findByPk;
const mockRentalFindOne = Rental.findOne;
const mockRentalCreate = Rental.create;

describe('Rentals Routes', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('GET /renting', () => {
    it('should get rentals for authenticated user', async () => {
      const mockRentals = [
        {
          id: 1,
          renterId: 1,
          item: { id: 1, name: 'Test Item' },
          owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' },
        },
        {
          id: 2,
          renterId: 1,
          item: { id: 2, name: 'Another Item' },
          owner: { id: 3, username: 'owner2', firstName: 'Jane', lastName: 'Smith' },
        },
      ];

      mockRentalFindAll.mockResolvedValue(mockRentals);

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

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockRentals);
      expect(mockRentalFindAll).toHaveBeenCalledWith({
        where: { renterId: 1 },
        include: [
          { model: Item, as: 'item' },
          {
            model: User,
            as: 'owner',
            attributes: ['id', 'firstName', 'lastName', 'imageFilename'],
          },
        ],
        order: [['createdAt', 'DESC']],
      });
    });

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

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

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to fetch rentals' });
    });
  });

  describe('GET /owning', () => {
    it('should get listings for authenticated user', async () => {
      const mockListings = [
        {
          id: 1,
          ownerId: 1,
          item: { id: 1, name: 'My Item' },
          renter: { id: 2, username: 'renter1', firstName: 'Alice', lastName: 'Johnson' },
        },
      ];

      mockRentalFindAll.mockResolvedValue(mockListings);

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

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockListings);
      expect(mockRentalFindAll).toHaveBeenCalledWith({
        where: { ownerId: 1 },
        include: [
          { model: Item, as: 'item' },
          {
            model: User,
            as: 'renter',
            attributes: ['id', 'firstName', 'lastName', 'imageFilename'],
          },
        ],
        order: [['createdAt', 'DESC']],
      });
    });

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

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

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to fetch listings' });
    });
  });

  describe('GET /:id', () => {
    const mockRental = {
      id: 1,
      ownerId: 2,
      renterId: 1,
      item: { id: 1, name: 'Test Item' },
      owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' },
      renter: { id: 1, username: 'renter1', firstName: 'Alice', lastName: 'Johnson' },
    };

    it('should get rental by ID for authorized user (renter)', async () => {
      mockRentalFindByPk.mockResolvedValue(mockRental);

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

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockRental);
    });

    it('should get rental by ID for authorized user (owner)', async () => {
      const ownerRental = { ...mockRental, ownerId: 1, renterId: 2 };
      mockRentalFindByPk.mockResolvedValue(ownerRental);

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

      expect(response.status).toBe(200);
      expect(response.body).toEqual(ownerRental);
    });

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

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

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

    it('should return 403 for unauthorized user', async () => {
      const unauthorizedRental = { ...mockRental, ownerId: 3, renterId: 4 };
      mockRentalFindByPk.mockResolvedValue(unauthorizedRental);

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

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

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

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

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to fetch rental' });
    });
  });

  describe('POST /', () => {
    // Helper to generate future dates for testing
    const getFutureDate = (daysFromNow, hours = 10) => {
      const date = new Date();
      date.setDate(date.getDate() + daysFromNow);
      date.setHours(hours, 0, 0, 0);
      return date.toISOString();
    };

    const mockItem = {
      id: 1,
      name: 'Test Item',
      ownerId: 2,
      isAvailable: true,
      pricePerHour: 10,
      pricePerDay: 50,
    };

    const mockCreatedRental = {
      id: 1,
      itemId: 1,
      renterId: 1,
      ownerId: 2,
      totalAmount: 120,
      platformFee: 20,
      payoutAmount: 100,
      status: 'pending',
    };

    const mockRentalWithDetails = {
      ...mockCreatedRental,
      item: mockItem,
      owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' },
      renter: { id: 1, username: 'renter1', firstName: 'Alice', lastName: 'Johnson' },
    };

    // Use dynamic future dates to avoid "Start date cannot be in the past" errors
    const rentalData = {
      itemId: 1,
      startDateTime: getFutureDate(7, 10),  // 7 days from now at 10:00
      endDateTime: getFutureDate(7, 18),     // 7 days from now at 18:00
      deliveryMethod: 'pickup',
      deliveryAddress: null,
      stripePaymentMethodId: 'pm_test123',
    };

    beforeEach(() => {
      Item.findByPk.mockResolvedValue(mockItem);
      mockRentalFindOne.mockResolvedValue(null); // No overlapping rentals
      mockRentalCreate.mockResolvedValue(mockCreatedRental);
      mockRentalFindByPk.mockResolvedValue(mockRentalWithDetails);
    });

    it('should create a new rental with hourly pricing', async () => {
      RentalDurationCalculator.calculateRentalCost.mockReturnValue(80); // 8 hours * 10/hour

      const response = await request(app)
        .post('/rentals')
        .send(rentalData);

      expect(response.status).toBe(201);
      expect(response.body).toEqual(mockRentalWithDetails);
      expect(FeeCalculator.calculateRentalFees).toHaveBeenCalledWith(80); // 8 hours * 10/hour
    });

    it('should create a new rental with daily pricing', async () => {
      RentalDurationCalculator.calculateRentalCost.mockReturnValue(150); // 3 days * 50/day

      const dailyRentalData = {
        ...rentalData,
        endDateTime: getFutureDate(10, 18), // 3 days from start (7 + 3 = 10 days from now)
      };

      const response = await request(app)
        .post('/rentals')
        .send(dailyRentalData);

      expect(response.status).toBe(201);
      expect(FeeCalculator.calculateRentalFees).toHaveBeenCalledWith(150); // 3 days * 50/day
    });

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

      const response = await request(app)
        .post('/rentals')
        .send(rentalData);

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

    it('should return 400 for unavailable item', async () => {
      Item.findByPk.mockResolvedValue({ ...mockItem, isAvailable: false });

      const response = await request(app)
        .post('/rentals')
        .send(rentalData);

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Item is not available' });
    });

    it('should return 400 for overlapping rental', async () => {
      mockRentalFindOne.mockResolvedValue({ id: 999 }); // Overlapping rental exists

      const response = await request(app)
        .post('/rentals')
        .send(rentalData);

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Item is already booked for these dates' });
    });

    it('should return 400 when payment method is missing for paid rentals', async () => {
      RentalDurationCalculator.calculateRentalCost.mockReturnValue(100); // Paid rental

      const dataWithoutPayment = { ...rentalData };
      delete dataWithoutPayment.stripePaymentMethodId;

      const response = await request(app)
        .post('/rentals')
        .send(dataWithoutPayment);

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Payment method is required for paid rentals' });
    });

    it('should create a free rental without payment method', async () => {
      RentalDurationCalculator.calculateRentalCost.mockReturnValue(0); // Free rental

      // Set up a free item (both prices are 0)
      Item.findByPk.mockResolvedValue({
        id: 1,
        ownerId: 2,
        isAvailable: true,
        pricePerHour: 0,
        pricePerDay: 0
      });

      const freeRentalData = { ...rentalData };
      delete freeRentalData.stripePaymentMethodId;

      const createdRental = {
        id: 1,
        ...freeRentalData,
        renterId: 1,
        ownerId: 2,
        totalAmount: 0,
        platformFee: 0,
        payoutAmount: 0,
        paymentStatus: 'not_required',
        status: 'pending'
      };

      Rental.create.mockResolvedValue(createdRental);
      Rental.findByPk.mockResolvedValue({
        ...createdRental,
        item: { id: 1, name: 'Free Item' },
        owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' },
        renter: { id: 1, username: 'renter1', firstName: 'Jane', lastName: 'Smith' }
      });

      const response = await request(app)
        .post('/rentals')
        .send(freeRentalData);

      expect(response.status).toBe(201);
      expect(response.body.paymentStatus).toBe('not_required');
      expect(response.body.totalAmount).toBe(0);
    });

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

      const response = await request(app)
        .post('/rentals')
        .send(rentalData);

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to create rental' });
    });
  });

  describe('PUT /:id/status', () => {
    const mockRental = {
      id: 1,
      ownerId: 1,
      renterId: 2,
      status: 'pending',
      stripePaymentMethodId: 'pm_test123',
      totalAmount: 120,
      item: { id: 1, name: 'Test Item' },
      renter: {
        id: 2,
        username: 'renter1',
        firstName: 'Alice',
        lastName: 'Johnson',
        stripeCustomerId: 'cus_test123'
      },
      update: jest.fn(),
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
    });

    it('should update rental status to confirmed without payment processing', async () => {
      const nonPendingRental = { ...mockRental, status: 'active' };
      mockRentalFindByPk.mockResolvedValueOnce(nonPendingRental);

      const updatedRental = { ...nonPendingRental, status: 'confirmed' };
      mockRentalFindByPk.mockResolvedValueOnce(updatedRental);

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(200);
      expect(nonPendingRental.update).toHaveBeenCalledWith({ status: 'confirmed' });
    });

    it('should process payment when owner approves pending rental', async () => {
      // Use the original mockRental (status: 'pending') for this test
      mockRentalFindByPk.mockResolvedValueOnce(mockRental);

      StripeService.chargePaymentMethod.mockResolvedValue({
        paymentIntentId: 'pi_test123',
        paymentMethod: {
          brand: 'visa',
          last4: '4242'
        },
        chargedAt: new Date('2024-01-15T10:00:00.000Z')
      });

      const updatedRental = {
        ...mockRental,
        status: 'confirmed',
        paymentStatus: 'paid',
        stripePaymentIntentId: 'pi_test123'
      };
      mockRentalFindByPk.mockResolvedValueOnce(updatedRental);

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(200);
      expect(StripeService.chargePaymentMethod).toHaveBeenCalledWith(
        'pm_test123',
        120,
        'cus_test123',
        expect.objectContaining({
          rentalId: 1,
          itemName: 'Test Item',
        })
      );
      expect(mockRental.update).toHaveBeenCalledWith({
        status: 'confirmed',
        paymentStatus: 'paid',
        stripePaymentIntentId: 'pi_test123',
        paymentMethodBrand: 'visa',
        paymentMethodLast4: '4242',
        chargedAt: new Date('2024-01-15T10:00:00.000Z'),
      });
    });

    it('should approve free rental without payment processing', async () => {
      const freeRental = {
        ...mockRental,
        totalAmount: 0,
        paymentStatus: 'not_required',
        stripePaymentMethodId: null,
        update: jest.fn().mockResolvedValue(true)
      };

      mockRentalFindByPk.mockResolvedValueOnce(freeRental);

      const updatedFreeRental = {
        ...freeRental,
        status: 'confirmed'
      };
      mockRentalFindByPk.mockResolvedValueOnce(updatedFreeRental);

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(200);
      expect(StripeService.chargePaymentMethod).not.toHaveBeenCalled();
      expect(freeRental.update).toHaveBeenCalledWith({
        status: 'confirmed'
      });
    });

    it('should return 400 when renter has no Stripe customer ID', async () => {
      const rentalWithoutStripeCustomer = {
        ...mockRental,
        renter: { ...mockRental.renter, stripeCustomerId: null }
      };
      mockRentalFindByPk.mockResolvedValue(rentalWithoutStripeCustomer);

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({
        error: 'Renter does not have a Stripe customer account'
      });
    });

    it('should handle payment failure during approval', async () => {
      StripeService.chargePaymentMethod.mockRejectedValue(
        new Error('Payment failed')
      );

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(402);
      expect(response.body).toEqual({
        error: 'payment_failed',
        code: 'unknown_error',
        ownerMessage: 'The payment could not be processed.',
        renterMessage: 'Your payment could not be processed. Please try a different payment method.',
        rentalId: 1,
      });
    });

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

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

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

    it('should return 403 for unauthorized user', async () => {
      const unauthorizedRental = { ...mockRental, ownerId: 3, renterId: 4 };
      mockRentalFindByPk.mockResolvedValue(unauthorizedRental);

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

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

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

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to update rental status' });
    });
  });

  describe('POST /:id/review-renter', () => {
    const mockRental = {
      id: 1,
      ownerId: 1,
      renterId: 2,
      status: 'completed',
      renterReviewSubmittedAt: null,
      update: jest.fn(),
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
    });

    it('should allow owner to review renter', async () => {
      const reviewData = {
        rating: 5,
        review: 'Great renter!',
        privateMessage: 'Thanks for taking care of my item',
      };

      mockRental.update.mockResolvedValue();

      const response = await request(app)
        .post('/rentals/1/review-renter')
        .send(reviewData);

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
      });
      expect(mockRental.update).toHaveBeenCalledWith({
        renterRating: 5,
        renterReview: 'Great renter!',
        renterReviewSubmittedAt: expect.any(Date),
        renterPrivateMessage: 'Thanks for taking care of my item',
      });
    });

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

      const response = await request(app)
        .post('/rentals/1/review-renter')
        .send({ rating: 5, review: 'Great!' });

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

    it('should return 403 for non-owner', async () => {
      const nonOwnerRental = { ...mockRental, ownerId: 3 };
      mockRentalFindByPk.mockResolvedValue(nonOwnerRental);

      const response = await request(app)
        .post('/rentals/1/review-renter')
        .send({ rating: 5, review: 'Great!' });

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Only owners can review renters' });
    });

    it('should return 400 for non-completed rental', async () => {
      const activeRental = { ...mockRental, status: 'active' };
      mockRentalFindByPk.mockResolvedValue(activeRental);

      const response = await request(app)
        .post('/rentals/1/review-renter')
        .send({ rating: 5, review: 'Great!' });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Can only review completed rentals' });
    });

    it('should return 400 if review already submitted', async () => {
      const reviewedRental = {
        ...mockRental,
        renterReviewSubmittedAt: new Date()
      };
      mockRentalFindByPk.mockResolvedValue(reviewedRental);

      const response = await request(app)
        .post('/rentals/1/review-renter')
        .send({ rating: 5, review: 'Great!' });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Renter review already submitted' });
    });

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

      const response = await request(app)
        .post('/rentals/1/review-renter')
        .send({ rating: 5, review: 'Great!' });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to submit review' });
    });
  });

  describe('POST /:id/review-item', () => {
    const mockRental = {
      id: 1,
      ownerId: 2,
      renterId: 1,
      status: 'completed',
      itemReviewSubmittedAt: null,
      update: jest.fn(),
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
    });

    it('should allow renter to review item', async () => {
      const reviewData = {
        rating: 4,
        review: 'Good item!',
        privateMessage: 'Item was as described',
      };

      mockRental.update.mockResolvedValue();

      const response = await request(app)
        .post('/rentals/1/review-item')
        .send(reviewData);

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
      });
      expect(mockRental.update).toHaveBeenCalledWith({
        itemRating: 4,
        itemReview: 'Good item!',
        itemReviewSubmittedAt: expect.any(Date),
        itemPrivateMessage: 'Item was as described',
      });
    });

    it('should return 403 for non-renter', async () => {
      const nonRenterRental = { ...mockRental, renterId: 3 };
      mockRentalFindByPk.mockResolvedValue(nonRenterRental);

      const response = await request(app)
        .post('/rentals/1/review-item')
        .send({ rating: 4, review: 'Good!' });

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Only renters can review items' });
    });

    it('should return 400 if review already submitted', async () => {
      const reviewedRental = {
        ...mockRental,
        itemReviewSubmittedAt: new Date()
      };
      mockRentalFindByPk.mockResolvedValue(reviewedRental);

      const response = await request(app)
        .post('/rentals/1/review-item')
        .send({ rating: 4, review: 'Good!' });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Item review already submitted' });
    });
  });

  describe('POST /calculate-fees', () => {
    it('should calculate fees for given amount', async () => {
      const response = await request(app)
        .post('/rentals/calculate-fees')
        .send({ totalAmount: 100 });

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        fees: {
          totalChargedAmount: 120,
          platformFee: 20,
          payoutAmount: 100,
        },
        display: {
          baseAmount: '$100.00',
          platformFee: '$20.00',
          totalAmount: '$120.00',
        },
      });
      expect(FeeCalculator.calculateRentalFees).toHaveBeenCalledWith(100);
      expect(FeeCalculator.formatFeesForDisplay).toHaveBeenCalled();
    });

    it('should return 400 for invalid amount', async () => {
      const response = await request(app)
        .post('/rentals/calculate-fees')
        .send({ totalAmount: 0 });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Valid base amount is required' });
    });

    it('should handle calculation errors', async () => {
      FeeCalculator.calculateRentalFees.mockImplementation(() => {
        throw new Error('Calculation error');
      });

      const response = await request(app)
        .post('/rentals/calculate-fees')
        .send({ totalAmount: 100 });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to calculate fees' });
    });
  });

  describe('GET /earnings/status', () => {
    it('should get earnings status for owner', async () => {
      const mockEarnings = [
        {
          id: 1,
          totalAmount: 120,
          platformFee: 20,
          payoutAmount: 100,
          payoutStatus: 'completed',
          payoutProcessedAt: '2024-01-15T10:00:00.000Z',
          stripeTransferId: 'tr_test123',
          item: { name: 'Test Item' },
        },
      ];

      mockRentalFindAll.mockResolvedValue(mockEarnings);

      const response = await request(app)
        .get('/rentals/earnings/status');

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockEarnings);
      expect(mockRentalFindAll).toHaveBeenCalledWith({
        where: {
          ownerId: 1,
          status: 'completed',
        },
        attributes: [
          'id',
          'totalAmount',
          'platformFee',
          'payoutAmount',
          'payoutStatus',
          'payoutProcessedAt',
          'stripeTransferId',
          'bankDepositStatus',
          'bankDepositAt',
          'bankDepositFailureCode',
        ],
        include: [{ model: Item, as: 'item', attributes: ['name'] }],
        order: [['createdAt', 'DESC']],
      });
    });

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

      const response = await request(app)
        .get('/rentals/earnings/status');

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

  describe('GET /:id/refund-preview', () => {
    it('should get refund preview', async () => {
      const mockPreview = {
        refundAmount: 80,
        refundPercentage: 80,
        reason: 'Cancelled more than 24 hours before start',
      };

      RefundService.getRefundPreview.mockResolvedValue(mockPreview);

      const response = await request(app)
        .get('/rentals/1/refund-preview');

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockPreview);
      expect(RefundService.getRefundPreview).toHaveBeenCalledWith('1', 1);
    });

    it('should handle refund service errors', async () => {
      RefundService.getRefundPreview.mockRejectedValue(
        new Error('Rental not found')
      );

      const response = await request(app)
        .get('/rentals/1/refund-preview');

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

  describe('POST /:id/cancel', () => {
    it('should cancel rental with refund', async () => {
      const mockResult = {
        rental: {
          id: 1,
          status: 'cancelled',
        },
        refund: {
          amount: 80,
          stripeRefundId: 'rf_test123',
        },
      };

      const mockUpdatedRental = {
        id: 1,
        status: 'cancelled',
        item: { id: 1, name: 'Test Item' },
        owner: { id: 2, username: 'owner1', firstName: 'John', lastName: 'Doe' },
        renter: { id: 1, username: 'renter1', firstName: 'Alice', lastName: 'Johnson' },
      };

      RefundService.processCancellation.mockResolvedValue(mockResult);
      mockRentalFindByPk.mockResolvedValue(mockUpdatedRental);

      const response = await request(app)
        .post('/rentals/1/cancel')
        .send({ reason: 'Change of plans' });

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        rental: mockUpdatedRental,
        refund: mockResult.refund,
      });
      expect(RefundService.processCancellation).toHaveBeenCalledWith(
        '1',
        1,
        'Change of plans'
      );
    });

    it('should handle cancellation errors', async () => {
      RefundService.processCancellation.mockRejectedValue(
        new Error('Cannot cancel completed rental')
      );

      const response = await request(app)
        .post('/rentals/1/cancel')
        .send({ reason: 'Change of plans' });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Cannot cancel completed rental' });
    });

    it('should return 400 when reason is not provided', async () => {
      const response = await request(app)
        .post('/rentals/1/cancel')
        .send({});

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Cancellation reason is required' });
    });
  });

  describe('GET /pending-requests-count', () => {
    it('should return count of pending requests for owner', async () => {
      Rental.count = jest.fn().mockResolvedValue(5);

      const response = await request(app)
        .get('/rentals/pending-requests-count');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({ count: 5 });
      expect(Rental.count).toHaveBeenCalledWith({
        where: {
          ownerId: 1,
          status: 'pending',
        },
      });
    });

    it('should handle database errors', async () => {
      Rental.count = jest.fn().mockRejectedValue(new Error('Database error'));

      const response = await request(app)
        .get('/rentals/pending-requests-count');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to get pending rental count' });
    });
  });

  describe('PUT /:id/decline', () => {
    const mockRental = {
      id: 1,
      ownerId: 1,
      renterId: 2,
      status: 'pending',
      item: { id: 1, name: 'Test Item' },
      owner: { id: 1, firstName: 'John', lastName: 'Doe' },
      renter: { id: 2, firstName: 'Alice', lastName: 'Johnson', email: 'alice@example.com' },
      update: jest.fn(),
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
    });

    it('should decline rental request with reason', async () => {
      mockRental.update.mockResolvedValue();
      mockRentalFindByPk
        .mockResolvedValueOnce(mockRental)
        .mockResolvedValueOnce({ ...mockRental, status: 'declined', declineReason: 'Item not available' });

      const response = await request(app)
        .put('/rentals/1/decline')
        .send({ reason: 'Item not available' });

      expect(response.status).toBe(200);
      expect(mockRental.update).toHaveBeenCalledWith({
        status: 'declined',
        declineReason: 'Item not available',
      });
    });

    it('should return 400 when reason is not provided', async () => {
      const response = await request(app)
        .put('/rentals/1/decline')
        .send({});

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'A reason for declining is required' });
    });

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

      const response = await request(app)
        .put('/rentals/1/decline')
        .send({ reason: 'Not available' });

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

    it('should return 403 for non-owner', async () => {
      const nonOwnerRental = { ...mockRental, ownerId: 3 };
      mockRentalFindByPk.mockResolvedValue(nonOwnerRental);

      const response = await request(app)
        .put('/rentals/1/decline')
        .send({ reason: 'Not available' });

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Only the item owner can decline rental requests' });
    });

    it('should return 400 for non-pending rental', async () => {
      const confirmedRental = { ...mockRental, status: 'confirmed' };
      mockRentalFindByPk.mockResolvedValue(confirmedRental);

      const response = await request(app)
        .put('/rentals/1/decline')
        .send({ reason: 'Not available' });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Can only decline pending rental requests' });
    });
  });

  describe('POST /cost-preview', () => {
    it('should return 400 for missing required fields', async () => {
      const response = await request(app)
        .post('/rentals/cost-preview')
        .send({ itemId: 1 });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'itemId, startDateTime, and endDateTime are required' });
    });
  });

  describe('GET /:id/late-fee-preview', () => {
    const LateReturnService = require('../../../services/lateReturnService');

    const mockRental = {
      id: 1,
      ownerId: 1,
      renterId: 2,
      endDateTime: new Date('2024-01-15T18:00:00.000Z'),
      item: { id: 1, name: 'Test Item' },
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
    });

    it('should return late fee preview', async () => {
      LateReturnService.calculateLateFee.mockReturnValue({
        isLate: true,
        hoursLate: 5,
        lateFee: 50,
      });

      const response = await request(app)
        .get('/rentals/1/late-fee-preview')
        .query({ actualReturnDateTime: '2024-01-15T23:00:00.000Z' });

      expect(response.status).toBe(200);
      expect(response.body.isLate).toBe(true);
      expect(response.body.lateFee).toBe(50);
    });

    it('should return 400 when actualReturnDateTime is missing', async () => {
      const response = await request(app)
        .get('/rentals/1/late-fee-preview');

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'actualReturnDateTime is required' });
    });

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

      const response = await request(app)
        .get('/rentals/1/late-fee-preview')
        .query({ actualReturnDateTime: '2024-01-15T23:00:00.000Z' });

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

    it('should return 403 for unauthorized user', async () => {
      const unauthorizedRental = { ...mockRental, ownerId: 3, renterId: 4 };
      mockRentalFindByPk.mockResolvedValue(unauthorizedRental);

      const response = await request(app)
        .get('/rentals/1/late-fee-preview')
        .query({ actualReturnDateTime: '2024-01-15T23:00:00.000Z' });

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

  describe('POST /:id/mark-return', () => {
    const mockRental = {
      id: 1,
      ownerId: 1,
      renterId: 2,
      status: 'confirmed',
      startDateTime: new Date('2024-01-10T10:00:00.000Z'),
      endDateTime: new Date('2024-01-15T18:00:00.000Z'),
      item: { id: 1, name: 'Test Item' },
      update: jest.fn().mockResolvedValue(),
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
    });

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

      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({ status: 'returned' });

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

    it('should return 403 for non-owner', async () => {
      const nonOwnerRental = { ...mockRental, ownerId: 3 };
      mockRentalFindByPk.mockResolvedValue(nonOwnerRental);

      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({ status: 'returned' });

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Only the item owner can mark return status' });
    });

    it('should return 400 for invalid status', async () => {
      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({ status: 'invalid_status' });

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

    it('should return 400 for non-active rental', async () => {
      const completedRental = { ...mockRental, status: 'completed' };
      mockRentalFindByPk.mockResolvedValue(completedRental);

      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({ status: 'returned' });

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

  describe('PUT /:id/payment-method', () => {
    const mockRental = {
      id: 1,
      ownerId: 2,
      renterId: 1,
      status: 'pending',
      paymentStatus: 'pending',
      stripePaymentMethodId: 'pm_old123',
      item: { id: 1, name: 'Test Item' },
      owner: { id: 2, firstName: 'John', email: 'john@example.com' },
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
      StripeService.getPaymentMethod = jest.fn().mockResolvedValue({
        id: 'pm_new123',
        customer: 'cus_test123',
      });
      User.findByPk = jest.fn().mockResolvedValue({
        id: 1,
        stripeCustomerId: 'cus_test123',
      });
      Rental.update = jest.fn().mockResolvedValue([1]);
    });

    it('should update payment method successfully', async () => {
      const response = await request(app)
        .put('/rentals/1/payment-method')
        .send({ stripePaymentMethodId: 'pm_new123' });

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    it('should return 400 when payment method ID is missing', async () => {
      const response = await request(app)
        .put('/rentals/1/payment-method')
        .send({});

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Payment method ID is required' });
    });

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

      const response = await request(app)
        .put('/rentals/1/payment-method')
        .send({ stripePaymentMethodId: 'pm_new123' });

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

    it('should return 403 for non-renter', async () => {
      const nonRenterRental = { ...mockRental, renterId: 3 };
      mockRentalFindByPk.mockResolvedValue(nonRenterRental);

      const response = await request(app)
        .put('/rentals/1/payment-method')
        .send({ stripePaymentMethodId: 'pm_new123' });

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Only the renter can update the payment method' });
    });

    it('should return 400 for non-pending rental', async () => {
      const confirmedRental = { ...mockRental, status: 'confirmed' };
      mockRentalFindByPk.mockResolvedValue(confirmedRental);

      const response = await request(app)
        .put('/rentals/1/payment-method')
        .send({ stripePaymentMethodId: 'pm_new123' });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Can only update payment method for pending rentals' });
    });
  });

  describe('POST /:id/report-damage', () => {
    const validUuid = '550e8400-e29b-41d4-a716-446655440000';
    const mockRental = {
      id: 1,
      ownerId: 1,
      renterId: 2,
      status: 'confirmed',
      item: { id: 1, name: 'Test Item' },
    };

    const mockDamageResult = {
      rental: { id: 1, status: 'damaged' },
      damageAssessment: {
        description: 'Screen cracked',
        feeCalculation: { amount: 150 },
      },
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
      DamageAssessmentService.processDamageAssessment.mockResolvedValue(mockDamageResult);
    });

    it('should report damage with all required fields', async () => {
      const damageData = {
        description: 'Screen cracked',
        canBeFixed: true,
        repairCost: 150,
      };

      const response = await request(app)
        .post('/rentals/1/report-damage')
        .send(damageData);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(DamageAssessmentService.processDamageAssessment).toHaveBeenCalledWith(
        '1',
        expect.objectContaining({ description: 'Screen cracked' }),
        1
      );
    });

    it('should report damage with optional late return', async () => {
      const damageResultWithLate = {
        ...mockDamageResult,
        lateCalculation: { lateFee: 50 },
      };
      DamageAssessmentService.processDamageAssessment.mockResolvedValue(damageResultWithLate);

      const damageData = {
        description: 'Screen cracked',
        canBeFixed: true,
        repairCost: 150,
        actualReturnDateTime: new Date().toISOString(),
      };

      const response = await request(app)
        .post('/rentals/1/report-damage')
        .send(damageData);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    it('should accept damage report without images', async () => {
      const damageData = {
        description: 'Screen cracked',
        canBeFixed: true,
        repairCost: 150,
        needsReplacement: false,
      };

      const response = await request(app)
        .post('/rentals/1/report-damage')
        .send(damageData);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    it('should return 400 for invalid imageFilenames format', async () => {
      const damageData = {
        description: 'Screen cracked',
        imageFilenames: ['invalid-key.jpg'],
      };

      const response = await request(app)
        .post('/rentals/1/report-damage')
        .send(damageData);

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

    it('should return 400 for non-image extensions', async () => {
      const damageData = {
        description: 'Screen cracked',
        imageFilenames: [`damage-reports/${validUuid}.exe`],
      };

      const response = await request(app)
        .post('/rentals/1/report-damage')
        .send(damageData);

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

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

      const damageData = {
        description: 'Screen cracked',
        imageFilenames: tooManyImages,
      };

      const response = await request(app)
        .post('/rentals/1/report-damage')
        .send(damageData);

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

    it('should handle damage assessment service errors', async () => {
      DamageAssessmentService.processDamageAssessment.mockRejectedValue(
        new Error('Assessment failed')
      );

      const response = await request(app)
        .post('/rentals/1/report-damage')
        .send({ description: 'Screen cracked' });

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

  describe('GET /:id/payment-client-secret', () => {
    const mockRental = {
      id: 1,
      ownerId: 2,
      renterId: 1,
      stripePaymentIntentId: 'pi_test123',
      renter: { id: 1, stripeCustomerId: 'cus_test123' },
    };

    let mockStripeInstance;

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
      mockStripeInstance = {
        paymentIntents: {
          retrieve: jest.fn().mockResolvedValue({
            client_secret: 'pi_test123_secret_xxx',
            status: 'requires_action',
          }),
        },
      };
      stripe.mockImplementation(() => mockStripeInstance);
    });

    it('should return client secret for renter', async () => {
      const response = await request(app)
        .get('/rentals/1/payment-client-secret');

      expect(response.status).toBe(200);
      expect(response.body.clientSecret).toBe('pi_test123_secret_xxx');
      expect(response.body.status).toBe('requires_action');
    });

    it('should return payment intent status', async () => {
      mockStripeInstance.paymentIntents.retrieve.mockResolvedValue({
        client_secret: 'pi_test123_secret_xxx',
        status: 'succeeded',
      });

      const response = await request(app)
        .get('/rentals/1/payment-client-secret');

      expect(response.status).toBe(200);
      expect(response.body.status).toBe('succeeded');
    });

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

      const response = await request(app)
        .get('/rentals/999/payment-client-secret');

      expect(response.status).toBe(404);
      expect(response.body.error).toBe('Rental not found');
    });

    it('should return 403 for non-renter', async () => {
      const nonRenterRental = { ...mockRental, renterId: 3 };
      mockRentalFindByPk.mockResolvedValue(nonRenterRental);

      const response = await request(app)
        .get('/rentals/1/payment-client-secret');

      expect(response.status).toBe(403);
      expect(response.body.error).toBe('Not authorized');
    });

    it('should return 400 when no payment intent exists', async () => {
      const rentalWithoutPaymentIntent = { ...mockRental, stripePaymentIntentId: null };
      mockRentalFindByPk.mockResolvedValue(rentalWithoutPaymentIntent);

      const response = await request(app)
        .get('/rentals/1/payment-client-secret');

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('No payment intent found');
    });

    it('should handle Stripe API errors', async () => {
      mockStripeInstance.paymentIntents.retrieve.mockRejectedValue(
        new Error('Stripe API error')
      );

      const response = await request(app)
        .get('/rentals/1/payment-client-secret');

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

  describe('POST /:id/complete-payment', () => {
    const mockRental = {
      id: 1,
      ownerId: 2,
      renterId: 1,
      status: 'pending',
      paymentStatus: 'requires_action',
      stripePaymentIntentId: 'pi_test123',
      totalAmount: 120,
      item: { id: 1, name: 'Test Item' },
      renter: { id: 1, firstName: 'Alice', lastName: 'Johnson', email: 'alice@example.com', stripeCustomerId: 'cus_test123' },
      owner: { id: 2, firstName: 'John', lastName: 'Doe', email: 'john@example.com', stripeConnectedAccountId: 'acct_test123', stripePayoutsEnabled: true },
      update: jest.fn().mockResolvedValue(),
    };

    let mockStripeInstance;

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
      mockRental.update.mockReset();
      mockStripeInstance = {
        paymentIntents: {
          retrieve: jest.fn().mockResolvedValue({
            status: 'succeeded',
            latest_charge: {
              payment_method_details: {
                type: 'card',
                card: { brand: 'visa', last4: '4242' },
              },
            },
          }),
        },
      };
      stripe.mockImplementation(() => mockStripeInstance);
    });

    it('should complete payment after 3DS authentication', async () => {
      const response = await request(app)
        .post('/rentals/1/complete-payment');

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.rental.status).toBe('confirmed');
      expect(response.body.rental.paymentStatus).toBe('paid');
    });

    it('should update rental to confirmed status', async () => {
      await request(app)
        .post('/rentals/1/complete-payment');

      expect(mockRental.update).toHaveBeenCalledWith({
        status: 'confirmed',
        paymentStatus: 'paid',
        chargedAt: expect.any(Date),
        paymentMethodBrand: 'visa',
        paymentMethodLast4: '4242',
      });
    });

    it('should create condition check schedules', async () => {
      await request(app)
        .post('/rentals/1/complete-payment');

      expect(EventBridgeSchedulerService.createConditionCheckSchedules).toHaveBeenCalled();
    });

    it('should trigger payout if owner has payouts enabled', async () => {
      await request(app)
        .post('/rentals/1/complete-payment');

      expect(PayoutService.processRentalPayout).toHaveBeenCalledWith(mockRental);
    });

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

      const response = await request(app)
        .post('/rentals/999/complete-payment');

      expect(response.status).toBe(404);
      expect(response.body.error).toBe('Rental not found');
    });

    it('should return 403 for non-renter', async () => {
      const nonRenterRental = { ...mockRental, renterId: 3 };
      mockRentalFindByPk.mockResolvedValue(nonRenterRental);

      const response = await request(app)
        .post('/rentals/1/complete-payment');

      expect(response.status).toBe(403);
      expect(response.body.error).toBe('Not authorized');
    });

    it('should return 400 when payment status is not requires_action', async () => {
      const paidRental = { ...mockRental, paymentStatus: 'paid' };
      mockRentalFindByPk.mockResolvedValue(paidRental);

      const response = await request(app)
        .post('/rentals/1/complete-payment');

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Invalid state');
    });

    it('should return 402 when payment intent not succeeded', async () => {
      mockStripeInstance.paymentIntents.retrieve.mockResolvedValue({
        status: 'requires_action',
      });

      const response = await request(app)
        .post('/rentals/1/complete-payment');

      expect(response.status).toBe(402);
      expect(response.body.error).toBe('payment_incomplete');
    });

    it('should handle Stripe API errors', async () => {
      mockStripeInstance.paymentIntents.retrieve.mockRejectedValue(
        new Error('Stripe API error')
      );

      const response = await request(app)
        .post('/rentals/1/complete-payment');

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

    it('should handle bank account payment methods', async () => {
      mockStripeInstance.paymentIntents.retrieve.mockResolvedValue({
        status: 'succeeded',
        latest_charge: {
          payment_method_details: {
            type: 'us_bank_account',
            us_bank_account: { last4: '6789' },
          },
        },
      });

      const response = await request(app)
        .post('/rentals/1/complete-payment');

      expect(response.status).toBe(200);
      expect(mockRental.update).toHaveBeenCalledWith(
        expect.objectContaining({
          paymentMethodBrand: 'bank_account',
          paymentMethodLast4: '6789',
        })
      );
    });
  });

  describe('POST /:id/mark-return (Additional Cases)', () => {
    let mockRental;

    const LateReturnService = require('../../../services/lateReturnService');

    beforeEach(() => {
      mockRental = {
        id: 1,
        ownerId: 1,
        renterId: 2,
        status: 'confirmed',
        startDateTime: new Date('2024-01-10T10:00:00.000Z'),
        endDateTime: new Date('2024-01-15T18:00:00.000Z'),
        lateFees: 0,
        item: { id: 1, name: 'Test Item' },
        update: jest.fn(),
      };
      // Make update return the modified rental instance
      mockRental.update.mockImplementation((updates) => {
        Object.assign(mockRental, updates);
        return Promise.resolve(mockRental);
      });
      mockRentalFindByPk.mockResolvedValue(mockRental);
    });

    it('should mark item as returned with payout trigger', async () => {
      mockRentalFindByPk.mockResolvedValueOnce(mockRental);
      const rentalWithDetails = {
        ...mockRental,
        owner: { id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com', stripeConnectedAccountId: 'acct_123' },
        renter: { id: 2, firstName: 'Alice', lastName: 'Johnson', email: 'alice@example.com' },
      };
      mockRentalFindByPk.mockResolvedValueOnce(rentalWithDetails);

      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({ status: 'returned' });

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(PayoutService.triggerPayoutOnCompletion).toHaveBeenCalledWith('1');
    });

    it('should mark item as damaged', async () => {
      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({ status: 'damaged' });

      expect(response.status).toBe(200);
      expect(mockRental.update).toHaveBeenCalledWith(
        expect.objectContaining({ status: 'damaged' })
      );
    });

    it('should mark item as returned_late with late fees', async () => {
      LateReturnService.processLateReturn.mockResolvedValue({
        rental: { ...mockRental, status: 'returned_late', lateFees: 50 },
        lateCalculation: { lateFee: 50, hoursLate: 5 },
      });

      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({
          status: 'returned_late',
          actualReturnDateTime: '2024-01-15T23:00:00.000Z',
        });

      expect(response.status).toBe(200);
      expect(response.body.lateCalculation).toBeDefined();
      expect(response.body.lateCalculation.lateFee).toBe(50);
    });

    it('should require actualReturnDateTime for late returns', async () => {
      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({ status: 'returned_late' });

      expect(response.status).toBe(400);
      expect(response.body.error).toBe('Actual return date/time is required for late returns');
    });

    it('should mark item as lost with customer service notification', async () => {
      User.findByPk = jest.fn().mockResolvedValue({
        id: 1,
        firstName: 'John',
        email: 'john@example.com',
      });

      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({ status: 'lost' });

      expect(response.status).toBe(200);
      expect(mockRental.update).toHaveBeenCalledWith(
        expect.objectContaining({
          status: 'lost',
          itemLostReportedAt: expect.any(Date),
        })
      );
    });

    it('should handle damaged with late return combination', async () => {
      LateReturnService.processLateReturn.mockResolvedValue({
        rental: { ...mockRental, lateFees: 50 },
        lateCalculation: { lateFee: 50, hoursLate: 5 },
      });

      const response = await request(app)
        .post('/rentals/1/mark-return')
        .send({
          status: 'damaged',
          actualReturnDateTime: '2024-01-15T23:00:00.000Z',
          statusOptions: { returned_late: true },
        });

      expect(response.status).toBe(200);
      expect(mockRental.update).toHaveBeenCalledWith(
        expect.objectContaining({ status: 'returned_late_and_damaged' })
      );
    });
  });

  describe('PUT /:id/status (3DS Flow)', () => {
    const mockRental = {
      id: 1,
      ownerId: 1,
      renterId: 2,
      status: 'pending',
      stripePaymentMethodId: 'pm_test123',
      totalAmount: 120,
      item: { id: 1, name: 'Test Item' },
      renter: {
        id: 2,
        username: 'renter1',
        firstName: 'Alice',
        lastName: 'Johnson',
        email: 'alice@example.com',
        stripeCustomerId: 'cus_test123',
      },
      owner: {
        id: 1,
        firstName: 'John',
        lastName: 'Doe',
        stripeConnectedAccountId: 'acct_test123',
      },
      update: jest.fn(),
    };

    beforeEach(() => {
      mockRentalFindByPk.mockResolvedValue(mockRental);
      mockRental.update.mockReset();
    });

    it('should handle payment requiring 3DS authentication', async () => {
      StripeService.chargePaymentMethod.mockResolvedValue({
        requiresAction: true,
        paymentIntentId: 'pi_test_3ds',
        clientSecret: 'pi_test_3ds_secret_xxx',
      });

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(402);
      expect(response.body.error).toBe('authentication_required');
      expect(response.body.requiresAction).toBe(true);
    });

    it('should return 402 with requiresAction flag', async () => {
      StripeService.chargePaymentMethod.mockResolvedValue({
        requiresAction: true,
        paymentIntentId: 'pi_test_3ds',
      });

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(402);
      expect(response.body.requiresAction).toBe(true);
      expect(response.body.rentalId).toBe(1);
    });

    it('should store payment intent ID for later completion', async () => {
      StripeService.chargePaymentMethod.mockResolvedValue({
        requiresAction: true,
        paymentIntentId: 'pi_test_3ds',
      });

      await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(mockRental.update).toHaveBeenCalledWith({
        stripePaymentIntentId: 'pi_test_3ds',
        paymentStatus: 'requires_action',
      });
    });

    it('should set paymentStatus to requires_action', async () => {
      StripeService.chargePaymentMethod.mockResolvedValue({
        requiresAction: true,
        paymentIntentId: 'pi_test_3ds',
      });

      await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(mockRental.update).toHaveBeenCalledWith(
        expect.objectContaining({ paymentStatus: 'requires_action' })
      );
    });

    it('should handle card declined errors', async () => {
      const declinedError = new Error('Your card was declined');
      declinedError.code = 'card_declined';

      StripeService.chargePaymentMethod.mockRejectedValue(declinedError);

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(402);
      expect(response.body.error).toBe('payment_failed');
      expect(response.body.code).toBe('card_declined');
    });

    it('should handle insufficient funds errors', async () => {
      const insufficientError = new Error('Insufficient funds');
      insufficientError.code = 'insufficient_funds';

      StripeService.chargePaymentMethod.mockRejectedValue(insufficientError);

      const response = await request(app)
        .put('/rentals/1/status')
        .send({ status: 'confirmed' });

      expect(response.status).toBe(402);
      expect(response.body.error).toBe('payment_failed');
    });
  });
});