// Mock CouchDB service for testing
const mockCouchdbService = {
  createDocument: jest.fn(),
  getDocument: jest.fn(),
  find: jest.fn(),
  destroy: jest.fn(),
  initialize: jest.fn(),
  findUserById: jest.fn(),
  update: jest.fn(),
};

// Mock the service module
jest.mock('../../services/couchdbService', () => mockCouchdbService);

const UserBadge = require('../../models/UserBadge');

describe('UserBadge Model', () => {
  beforeEach(() => {
    // Reset all mocks to ensure clean state
    mockCouchdbService.createDocument.mockReset();
    mockCouchdbService.getDocument.mockReset();
    mockCouchdbService.find.mockReset();
    mockCouchdbService.destroy.mockReset();
  });

  describe('Schema Validation', () => {
    it('should create a valid user badge', async () => {
      const userBadgeData = {
        user: 'user_123',
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
      };

      const mockCreated = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        ...userBadgeData,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const userBadge = await UserBadge.create(userBadgeData);

      expect(userBadge._id).toBeDefined();
      expect(userBadge.user).toBe(userBadgeData.user);
      expect(userBadge.badge).toBe(userBadgeData.badge);
      expect(userBadge.earnedAt).toBe(userBadgeData.earnedAt);
    });

    it('should require user field', async () => {
      const userBadgeData = {
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
      };

      expect(() => new UserBadge(userBadgeData)).toThrow();
    });

    it('should require badge field', async () => {
      const userBadgeData = {
        user: 'user_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
      };

      expect(() => new UserBadge(userBadgeData)).toThrow();
    });

    it('should require earnedAt field', async () => {
      const userBadgeData = {
        user: 'user_123',
        badge: 'badge_123',
      };

      expect(() => UserBadge.validateWithEarnedAt(userBadgeData)).toThrow();
    });
  });

  describe('Default Values', () => {
    it('should default earnedAt to current time if not provided', async () => {
      const userBadgeData = {
        user: 'user_123',
        badge: 'badge_123',
      };

      const mockCreated = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        ...userBadgeData,
        earnedAt: '2023-11-01T10:00:00.000Z', // Default current time
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const userBadge = await UserBadge.create(userBadgeData);

      expect(userBadge.earnedAt).toBeDefined();
      expect(typeof userBadge.earnedAt).toBe('string');
    });
  });

  describe('Unique Constraint', () => {
    it('should enforce unique user-badge combination', async () => {
      const userBadgeData = {
        user: 'user_123',
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
      };

      // Simulate existing user badge
      const existingUserBadge = {
        _id: 'user_badge_existing',
        _rev: '1-abc',
        type: 'user_badge',
        ...userBadgeData,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.find.mockResolvedValue({ docs: [existingUserBadge] });

      // This should be handled at the service level, but we test the model validation
      const mockCreated = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        ...userBadgeData,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const userBadge = await UserBadge.create(userBadgeData);

      expect(userBadge.user).toBe('user_123');
      expect(userBadge.badge).toBe('badge_123');
    });
  });

  describe('Date Validation', () => {
    it('should accept valid date strings for earnedAt', async () => {
      const validDates = [
        '2023-11-01T10:00:00.000Z',
        '2023-12-15T14:30:00.000Z',
        '2024-01-20T09:15:00.000Z'
      ];

      for (const date of validDates) {
        const userBadgeData = {
          user: 'user_123',
          badge: 'badge_123',
          earnedAt: date,
        };

        const mockCreated = {
          _id: 'user_badge_123',
          _rev: '1-abc',
          type: 'user_badge',
          ...userBadgeData,
          createdAt: '2023-01-01T00:00:00.000Z',
          updatedAt: '2023-01-01T00:00:00.000Z'
        };

      mockCouchdbService.createDocument.mockResolvedValue({ rev: '1-abc' });

        const userBadge = await UserBadge.create(userBadgeData);

        expect(userBadge.earnedAt).toBe(date);
      }
    });
  });

  describe('Relationships', () => {
    it('should reference user ID', async () => {
      const userBadgeData = {
        user: 'user_123',
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
      };

      const mockCreated = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        ...userBadgeData,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const userBadge = await UserBadge.create(userBadgeData);

      expect(userBadge.user).toBe('user_123');
    });

    it('should reference badge ID', async () => {
      const userBadgeData = {
        user: 'user_123',
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
      };

      const mockCreated = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        ...userBadgeData,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const userBadge = await UserBadge.create(userBadgeData);

      expect(userBadge.badge).toBe('badge_123');
    });
  });

  describe('Timestamps', () => {
    it('should automatically set createdAt and updatedAt', async () => {
      const userBadgeData = {
        user: 'user_123',
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
      };

      const mockCreated = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        ...userBadgeData,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const userBadge = await UserBadge.create(userBadgeData);

      expect(userBadge.createdAt).toBeDefined();
      expect(userBadge.updatedAt).toBeDefined();
      expect(typeof userBadge.createdAt).toBe('string');
      expect(typeof userBadge.updatedAt).toBe('string');
    });

    it('should update updatedAt on modification', async () => {
      const userBadgeData = {
        user: 'user_123',
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
      };

      const mockUserBadge = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        ...userBadgeData,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.getDocument.mockResolvedValue(mockUserBadge);
      mockCouchdbService.createDocument.mockResolvedValue({ rev: '2-def' });

      const userBadge = await UserBadge.findById('user_badge_123');
      const originalUpdatedAt = userBadge.updatedAt;

      const updatedUserBadge = await UserBadge.update('user_badge_123', { earnedAt: '2023-11-02T10:00:00.000Z' });

      expect(updatedUserBadge.updatedAt).not.toBe(originalUpdatedAt);
    });
  });

  describe('Static Methods', () => {
    it('should find user badge by ID', async () => {
      const mockUserBadge = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        user: 'user_123',
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.getDocument.mockResolvedValue(mockUserBadge);

      const userBadge = await UserBadge.findById('user_badge_123');
      expect(userBadge).toBeDefined();
      expect(userBadge._id).toBe('user_badge_123');
      expect(userBadge.user).toBe('user_123');
      expect(userBadge.badge).toBe('badge_123');
    });

    it('should return null when user badge not found', async () => {
      mockCouchdbService.getDocument.mockResolvedValue(null);

      const userBadge = await UserBadge.findById('nonexistent');
      expect(userBadge).toBeNull();
    });

    it('should find badges by user ID', async () => {
      const mockUserBadges = [
        {
          _id: 'user_badge_1',
          _rev: '1-abc',
          type: 'user_badge',
          user: 'user_123',
          badge: 'badge_1',
          earnedAt: '2023-11-01T10:00:00.000Z',
          createdAt: '2023-01-01T00:00:00.000Z',
          updatedAt: '2023-01-01T00:00:00.000Z'
        },
        {
          _id: 'user_badge_2',
          _rev: '1-abc',
          type: 'user_badge',
          user: 'user_123',
          badge: 'badge_2',
          earnedAt: '2023-11-02T10:00:00.000Z',
          createdAt: '2023-01-01T00:00:00.000Z',
          updatedAt: '2023-01-01T00:00:00.000Z'
        }
      ];

      mockCouchdbService.find.mockResolvedValue({ docs: mockUserBadges });

      const userBadges = await UserBadge.findByUser('user_123');
      expect(userBadges).toHaveLength(2);
      expect(userBadges[0].user).toBe('user_123');
      expect(userBadges[1].user).toBe('user_123');
    });

    it('should find users by badge ID', async () => {
      const mockUserBadges = [
        {
          _id: 'user_badge_1',
          _rev: '1-abc',
          type: 'user_badge',
          user: 'user_123',
          badge: 'badge_123',
          earnedAt: '2023-11-01T10:00:00.000Z',
          createdAt: '2023-01-01T00:00:00.000Z',
          updatedAt: '2023-01-01T00:00:00.000Z'
        },
        {
          _id: 'user_badge_2',
          _rev: '1-abc',
          type: 'user_badge',
          user: 'user_456',
          badge: 'badge_123',
          earnedAt: '2023-11-02T10:00:00.000Z',
          createdAt: '2023-01-01T00:00:00.000Z',
          updatedAt: '2023-01-01T00:00:00.000Z'
        }
      ];

      mockCouchdbService.find.mockResolvedValue({ docs: mockUserBadges });

      const userBadges = await UserBadge.findByBadge('badge_123');
      expect(userBadges).toHaveLength(2);
      expect(userBadges[0].badge).toBe('badge_123');
      expect(userBadges[1].badge).toBe('badge_123');
    });
  });

  describe('Helper Methods', () => {
    it('should check if user has specific badge', async () => {
      const mockUserBadge = {
        _id: 'user_badge_123',
        _rev: '1-abc',
        type: 'user_badge',
        user: 'user_123',
        badge: 'badge_123',
        earnedAt: '2023-11-01T10:00:00.000Z',
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.find.mockResolvedValue({ docs: [mockUserBadge] });

      const hasBadge = await UserBadge.userHasBadge('user_123', 'badge_123');
      expect(hasBadge).toBe(true);
    });

    it('should return false if user does not have specific badge', async () => {
      mockCouchdbService.find.mockResolvedValue({ docs: [] });

      const hasBadge = await UserBadge.userHasBadge('user_123', 'badge_456');
      expect(hasBadge).toBe(false);
    });
  });
});
