// Mock CouchDB service for testing
const mockCouchdbService = {
  find: jest.fn(),
  get: jest.fn(),
  getDocument: jest.fn(),
  createDocument: jest.fn(),
  destroy: jest.fn(),
  initialize: jest.fn(),
  isReady: jest.fn().mockReturnValue(true),
  shutdown: jest.fn()
};

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

const couchdbService = require('../../services/couchdbService');
const Badge = require('../../models/Badge');

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

  describe('findAll', () => {
    it('should return all badges sorted by order', async () => {
      const mockBadges = [
        { _id: 'badge1', type: 'badge', name: 'First Badge', order: 1 },
        { _id: 'badge2', type: 'badge', name: 'Second Badge', order: 2 }
      ];

      couchdbService.find.mockResolvedValue({ docs: mockBadges });

      const badges = await Badge.findAll();

      expect(couchdbService.find).toHaveBeenCalledWith({
        selector: { type: 'badge' },
        sort: [{ order: 'asc' }]
      });
      expect(badges).toEqual(mockBadges);
    });

    it('should handle errors when finding badges', async () => {
      couchdbService.find.mockRejectedValue(new Error('Database error'));

      await expect(Badge.findAll()).rejects.toThrow('Database error');
    });
  });

  describe('findById', () => {
    it('should return badge by ID', async () => {
      const mockBadge = {
        _id: 'badge_123',
        type: 'badge',
        name: 'Test Badge',
        description: 'Test description'
      };

      couchdbService.get.mockResolvedValue(mockBadge);

      const badge = await Badge.findById('badge_123');

      expect(couchdbService.get).toHaveBeenCalledWith('badge_123');
      expect(badge).toEqual(mockBadge);
    });

    it('should return null for non-badge documents', async () => {
      const mockDoc = {
        _id: 'user_123',
        type: 'user',
        name: 'Test User'
      };

      couchdbService.get.mockResolvedValue(mockDoc);

      const badge = await Badge.findById('user_123');

      expect(badge).toBeNull();
    });

    it('should return null for 404 errors', async () => {
      const error = new Error('Not found');
      error.statusCode = 404;
      couchdbService.get.mockRejectedValue(error);

      const badge = await Badge.findById('nonexistent');

      expect(badge).toBeNull();
    });

    it('should handle other errors', async () => {
      couchdbService.get.mockRejectedValue(new Error('Database error'));

      await expect(Badge.findById('badge_123')).rejects.toThrow('Database error');
    });
  });

  describe('create', () => {
    it('should create a new badge', async () => {
      const badgeData = {
        name: 'Street Cleaner',
        description: 'Awarded for completing 10 street cleaning tasks',
        icon: 'broom',
        criteria: { type: 'task_count', threshold: 10 },
        rarity: 'common',
        order: 1
      };

      const mockResult = { rev: '1-abc' };
      couchdbService.createDocument.mockResolvedValue(mockResult);

      const badge = await Badge.create(badgeData);

      expect(couchdbService.createDocument).toHaveBeenCalledWith(
        expect.objectContaining({
          _id: expect.stringMatching(/^badge_\d+_[a-z0-9]+$/),
          type: 'badge',
          name: badgeData.name,
          description: badgeData.description,
          icon: badgeData.icon,
          criteria: badgeData.criteria,
          rarity: badgeData.rarity,
          order: badgeData.order,
          isActive: true,
          createdAt: expect.any(String),
          updatedAt: expect.any(String)
        })
      );
      expect(badge).toEqual(
        expect.objectContaining({
          ...badgeData,
          _rev: '1-abc',
          isActive: true,
          createdAt: expect.any(String),
          updatedAt: expect.any(String)
        })
      );
    });

    it('should use default values', async () => {
      const badgeData = {
        name: 'Test Badge',
        description: 'Test description',
        icon: 'star'
      };

      const mockResult = { rev: '1-abc' };
      couchdbService.createDocument.mockResolvedValue(mockResult);

      const badge = await Badge.create(badgeData);

      expect(badge.rarity).toBe('common');
      expect(badge.order).toBe(0);
      expect(badge.isActive).toBe(true);
    });

    it('should handle errors during creation', async () => {
      const badgeData = {
        name: 'Test Badge',
        description: 'Test description',
        icon: 'star'
      };

      couchdbService.createDocument.mockRejectedValue(new Error('Creation failed'));

      await expect(Badge.create(badgeData)).rejects.toThrow('Creation failed');
    });
  });

  describe('update', () => {
    it('should update an existing badge', async () => {
      const existingBadge = {
        _id: 'badge_123',
        _rev: '1-abc',
        type: 'badge',
        name: 'Original Name',
        description: 'Original description',
        icon: 'star',
        criteria: { type: 'task_count', threshold: 5 },
        rarity: 'common',
        order: 0,
        isActive: true,
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      const updateData = {
        name: 'Updated Name',
        rarity: 'rare'
      };

      const mockResult = { rev: '2-def' };
      couchdbService.get.mockResolvedValue(existingBadge);
      couchdbService.createDocument.mockResolvedValue(mockResult);

      const badge = await Badge.update('badge_123', updateData);

      expect(couchdbService.get).toHaveBeenCalledWith('badge_123');
      expect(couchdbService.createDocument).toHaveBeenCalledWith(
        expect.objectContaining({
          ...existingBadge,
          name: 'Updated Name',
          rarity: 'rare',
          updatedAt: expect.any(String)
        })
      );
      expect(badge._rev).toBe('2-def');
    });

    it('should throw error for non-badge documents', async () => {
      const nonBadgeDoc = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user'
      };

      couchdbService.get.mockResolvedValue(nonBadgeDoc);

      await expect(Badge.update('user_123', { name: 'Updated' })).rejects.toThrow('Document is not a badge');
    });

    it('should handle errors during update', async () => {
      couchdbService.get.mockRejectedValue(new Error('Update failed'));

      await expect(Badge.update('badge_123', { name: 'Updated' })).rejects.toThrow('Update failed');
    });
  });

  describe('delete', () => {
    it('should delete a badge', async () => {
      const mockBadge = {
        _id: 'badge_123',
        _rev: '1-abc',
        type: 'badge'
      };

      couchdbService.get.mockResolvedValue(mockBadge);
      couchdbService.destroy.mockResolvedValue(true);

      const result = await Badge.delete('badge_123');

      expect(couchdbService.get).toHaveBeenCalledWith('badge_123');
      expect(couchdbService.destroy).toHaveBeenCalledWith('badge_123', '1-abc');
      expect(result).toBe(true);
    });

    it('should throw error for non-badge documents', async () => {
      const nonBadgeDoc = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user'
      };

      couchdbService.get.mockResolvedValue(nonBadgeDoc);

      await expect(Badge.delete('user_123')).rejects.toThrow('Document is not a badge');
    });

    it('should handle errors during deletion', async () => {
      couchdbService.get.mockRejectedValue(new Error('Delete failed'));

      await expect(Badge.delete('badge_123')).rejects.toThrow('Delete failed');
    });
  });

  describe('findByCriteria', () => {
    it('should find badges by criteria type and threshold', async () => {
      const mockBadges = [
        { _id: 'badge1', type: 'badge', criteria: { type: 'task_count', threshold: 10 } },
        { _id: 'badge2', type: 'badge', criteria: { type: 'task_count', threshold: 5 } }
      ];

      couchdbService.find.mockResolvedValue({ docs: mockBadges });

      const badges = await Badge.findByCriteria('task_count', 7);

      expect(couchdbService.find).toHaveBeenCalledWith({
        selector: {
          type: 'badge',
          'criteria.type': 'task_count',
          'criteria.threshold': { $lte: 7 }
        },
        sort: [{ 'criteria.threshold': 'desc' }]
      });
      expect(badges).toEqual(mockBadges);
    });

    it('should handle errors when finding by criteria', async () => {
      couchdbService.find.mockRejectedValue(new Error('Search failed'));

      await expect(Badge.findByCriteria('task_count', 10)).rejects.toThrow('Search failed');
    });
  });

  describe('findByRarity', () => {
    it('should find badges by rarity', async () => {
      const mockBadges = [
        { _id: 'badge1', type: 'badge', rarity: 'rare', order: 1 },
        { _id: 'badge2', type: 'badge', rarity: 'rare', order: 2 }
      ];

      couchdbService.find.mockResolvedValue({ docs: mockBadges });

      const badges = await Badge.findByRarity('rare');

      expect(couchdbService.find).toHaveBeenCalledWith({
        selector: {
          type: 'badge',
          rarity: 'rare'
        },
        sort: [{ order: 'asc' }]
      });
      expect(badges).toEqual(mockBadges);
    });

    it('should handle errors when finding by rarity', async () => {
      couchdbService.find.mockRejectedValue(new Error('Search failed'));

      await expect(Badge.findByRarity('rare')).rejects.toThrow('Search failed');
    });
  });
});