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

// Mock CouchDB service before importing routes
jest.mock('../../services/couchdbService', () => ({
  initialize: jest.fn().mockResolvedValue(true),
  create: jest.fn(),
  getById: jest.fn(),
  find: jest.fn(),
  createDocument: jest.fn().mockImplementation((doc) => Promise.resolve({
    _id: `reward_${Date.now()}`,
    _rev: '1-test',
    type: 'reward',
    ...doc
  })),
  updateDocument: jest.fn().mockImplementation((id, doc) => Promise.resolve({
    _id: id,
    _rev: '2-test',
    ...doc
  })),
  deleteDocument: jest.fn(),
  findByType: jest.fn().mockResolvedValue([]),
  findUserById: jest.fn(),
  findUserByEmail: jest.fn(),
  update: jest.fn(),
  getDocument: jest.fn(),
}));

const rewardsRoutes = require('../../routes/rewards');
const Reward = require('../../models/Reward');
const User = require('../../models/User');
const { createTestUser, createTestReward } = require('../utils/testHelpers');
const couchdbService = require('../../services/couchdbService');

// Create Express app for testing
const app = express();
app.use(express.json());
app.use('/api/rewards', rewardsRoutes);

describe('Rewards Routes', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    
    // Mock Reward model methods
    Reward.getAllPaginated = jest.fn().mockResolvedValue({
      rewards: [],
      pagination: { totalCount: 0 }
    });
    Reward.findById = jest.fn().mockResolvedValue(null);
    Reward.create = jest.fn().mockImplementation((data) => Promise.resolve({
      _id: `reward_${Date.now()}`,
      _rev: '1-test',
      type: 'reward',
      ...data
    }));
    Reward.redeemReward = jest.fn().mockResolvedValue({
      pointsDeducted: 50,
      newBalance: 50,
      redemption: {
        _id: 'redemption_123',
        userId: 'user_123',
        rewardId: 'reward_123'
      }
    });
    
    // Mock User model methods
    User.findById = jest.fn().mockResolvedValue({
      _id: 'user_123',
      points: 100,
      isPremium: false,
      redeemedRewards: [],
      stats: {
        streetsAdopted: 0,
        tasksCompleted: 0,
        postsCreated: 0,
        eventsParticipated: 0,
        badgesEarned: 0
      }
    });
    User.update = jest.fn().mockResolvedValue(true);
    
    // Mock couchdbService methods
    couchdbService.find.mockResolvedValue([]);
    couchdbService.getDocument.mockResolvedValue(null);
  });

  describe('GET /api/rewards', () => {
    it('should get all rewards', async () => {
      const reward1 = await createTestReward({ name: 'Reward 1', cost: 50 });
      const reward2 = await createTestReward({ name: 'Reward 2', cost: 100 });

      // Mock Reward.getAllPaginated to return test rewards
      Reward.getAllPaginated.mockResolvedValue({
        rewards: [reward1, reward2],
        pagination: { totalCount: 2 }
      });

      const response = await request(app)
        .get('/api/rewards')
        .expect(200);

      expect(Array.isArray(response.body.data)).toBe(true);
      expect(response.body.data.length).toBe(2);
      expect(response.body.data[0]).toHaveProperty('name');
      expect(response.body.data[0]).toHaveProperty('cost');
    });

    it('should return empty array when no rewards exist', async () => {
      // Mock Reward.getAllPaginated to return empty array
      Reward.getAllPaginated.mockResolvedValue({
        rewards: [],
        pagination: { totalCount: 0 }
      });

      const response = await request(app)
        .get('/api/rewards')
        .expect(200);

      expect(Array.isArray(response.body.data)).toBe(true);
      expect(response.body.data.length).toBe(0);
    });
  });

  describe('POST /api/rewards', () => {
    it('should create a new reward with authentication', async () => {
      const { token } = await createTestUser();
      const rewardData = {
        name: 'Test Reward',
        description: 'Test reward description',
        cost: 50,
      };

      // Mock Reward.create to return created reward
      const createdReward = {
        _id: 'reward_123',
        _rev: '1-test',
        type: 'reward',
        ...rewardData,
        isActive: true,
        isPremium: false,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };
      Reward.create.mockResolvedValue(createdReward);

      const response = await request(app)
        .post('/api/rewards')
        .set('x-auth-token', token)
        .send(rewardData)
        .expect(200);

      expect(response.body).toHaveProperty('_id');
      expect(response.body.name).toBe(rewardData.name);
      expect(response.body.description).toBe(rewardData.description);
      expect(response.body.cost).toBe(rewardData.cost);
    });

    it('should reject reward creation without authentication', async () => {
      const rewardData = {
        name: 'Unauthorized Reward',
        description: 'This should fail',
        cost: 50,
      };

      const response = await request(app)
        .post('/api/rewards')
        .send(rewardData)
        .expect(401);

      expect(response.body).toHaveProperty('msg', 'No token, authorization denied');
    });
  });

  describe('POST /api/rewards/redeem/:id', () => {
    it('should allow user to redeem reward with sufficient points', async () => {
      const { user, token } = await createTestUser();
      const reward = await createTestReward({ cost: 50 });

      // Ensure reward ID matches validator pattern by manually setting it
      reward.id = 'reward_test123';
      reward._id = 'reward_test123';

      // Mock Reward.redeemReward to return successful redemption
      Reward.redeemReward.mockResolvedValue({
        pointsDeducted: 50,
        newBalance: 50,
        redemption: {
          _id: 'redemption_123',
          userId: user._id,
          rewardId: reward._id
        }
      });

      const response = await request(app)
        .post(`/api/rewards/redeem/${reward.id}`)
        .set('x-auth-token', token)
        .expect(200);

      expect(response.body).toHaveProperty('msg', 'Reward redeemed successfully');
      expect(response.body).toHaveProperty('pointsDeducted', 50);
      expect(response.body).toHaveProperty('newBalance', 50);
    });

    it('should reject redemption without sufficient points', async () => {
      const { user, token } = await createTestUser();
      const reward = await createTestReward({ cost: 150 });
      reward.id = 'reward_test456';
      reward._id = 'reward_test456';

      // Mock Reward.redeemReward to throw "Not enough points" error
      Reward.redeemReward.mockRejectedValue(new Error('Not enough points'));

      const response = await request(app)
        .post(`/api/rewards/redeem/${reward.id}`)
        .set('x-auth-token', token)
        .expect(400);

      expect(response.body).toHaveProperty('msg', 'Not enough points');
    });

    it('should reject premium reward redemption for non-premium users', async () => {
      const { user, token } = await createTestUser();
      const reward = await createTestReward({ 
        cost: 50, 
        isPremium: true 
      });
      reward.id = 'reward_test789';
      reward._id = 'reward_test789';

      // Mock Reward.redeemReward to throw "Premium reward not available" error
      Reward.redeemReward.mockRejectedValue(new Error('Premium reward not available'));

      const response = await request(app)
        .post(`/api/rewards/redeem/${reward.id}`)
        .set('x-auth-token', token)
        .expect(403);

      expect(response.body).toHaveProperty('msg', 'Premium reward not available');
    });

    it('should allow premium users to redeem premium rewards', async () => {
      const { user, token } = await createTestUser({ isPremium: true });
      const reward = await createTestReward({ 
        cost: 50, 
        isPremium: true 
      });
      reward.id = 'reward_test999';
      reward._id = 'reward_test999';

      // Mock Reward.redeemReward to return successful redemption
      Reward.redeemReward.mockResolvedValue({
        pointsDeducted: 50,
        newBalance: 50,
        redemption: {
          _id: 'redemption_123',
          userId: user._id,
          rewardId: reward._id
        }
      });

      const response = await request(app)
        .post(`/api/rewards/redeem/${reward.id}`)
        .set('x-auth-token', token)
        .expect(200);

      expect(response.body).toHaveProperty('msg', 'Reward redeemed successfully');
      expect(response.body).toHaveProperty('pointsDeducted', 50);
      expect(response.body).toHaveProperty('newBalance', 50);
    });

    it('should return 404 for non-existent reward', async () => {
      const { token } = await createTestUser();
      const fakeId = '507f1f77bcf86cd799439011';

      // Mock Reward.redeemReward to throw "Reward not found" error
      Reward.redeemReward.mockRejectedValue(new Error('Reward not found'));

      const response = await request(app)
        .post(`/api/rewards/redeem/${fakeId}`)
        .set('x-auth-token', token)
        .expect(404);

      expect(response.body).toHaveProperty('msg', 'Reward not found');
    });

    it('should reject redemption without authentication', async () => {
      const { user } = await createTestUser();
      const reward = await createTestReward({ cost: 50 });

      const response = await request(app)
        .post(`/api/rewards/redeem/${reward.id}`)
        .expect(401);

      expect(response.body).toHaveProperty('msg', 'No token, authorization denied');
    });

    it('should handle invalid reward ID format', async () => {
      const { token } = await createTestUser();

      const response = await request(app)
        .post('/api/rewards/redeem/invalid-id')
        .set('x-auth-token', token)
        .expect(400);

      expect(response.body).toHaveProperty('success', false);
      expect(response.body.errors).toBeDefined();
    });
  });
});