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: 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(),
  },
  rentalReminder: {
    sendUpcomingRentalReminder: jest.fn(),
  },
}));

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(),
}));

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(),
}));

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');

// 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(400);
      expect(response.body).toEqual({
        error: 'Payment failed during approval',
        details: 'Payment failed',
      });
    });

    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 /:id/mark-completed', () => {
    // Active status is computed: confirmed + startDateTime in the past
    const pastDate = new Date();
    pastDate.setHours(pastDate.getHours() - 1); // 1 hour ago
    const mockRental = {
      id: 1,
      ownerId: 1,
      renterId: 2,
      status: 'confirmed',
      startDateTime: pastDate,
      update: jest.fn(),
    };

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

    it('should allow owner to mark rental as completed', async () => {
      const completedRental = { ...mockRental, status: 'completed' };
      mockRentalFindByPk
        .mockResolvedValueOnce(mockRental)
        .mockResolvedValueOnce(completedRental);

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

      expect(response.status).toBe(200);
      expect(mockRental.update).toHaveBeenCalledWith({ status: 'completed', payoutStatus: 'pending' });
    });

    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-completed');

      expect(response.status).toBe(403);
      expect(response.body).toEqual({
        error: 'Only owners can mark rentals as completed'
      });
    });

    it('should return 400 for invalid status', async () => {
      const pendingRental = { ...mockRental, status: 'pending' };
      mockRentalFindByPk.mockResolvedValue(pendingRental);

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

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

  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',
        ],
        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' });
    });
  });
});