// Mock CouchDB service for testing
const mockCouchdbService = {
  findUserByEmail: jest.fn(),
  findUserById: jest.fn(),
  createDocument: jest.fn(),
  updateDocument: jest.fn(),
  findByType: jest.fn(),
  deleteDocument: jest.fn(),
  initialize: jest.fn(),
  isReady: jest.fn().mockReturnValue(true),
  shutdown: jest.fn()
};

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

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

describe('User Model', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    // Reset all mocks to ensure clean state
    mockCouchdbService.findUserByEmail.mockReset();
    mockCouchdbService.findUserById.mockReset();
    mockCouchdbService.createDocument.mockReset();
    mockCouchdbService.updateDocument.mockReset();
  });

  describe('Schema Validation', () => {
    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should create a valid user', async () => {
      const userData = {
        name: 'Test User',
        email: 'test@example.com',
        password: 'password123',
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);

      expect(user._id).toBeDefined();
      expect(user.name).toBe(userData.name);
      expect(user.email).toBe(userData.email);
      expect(user.isPremium).toBe(false);
      expect(user.points).toBe(0);
      expect(user.adoptedStreets).toEqual([]);
      expect(user.completedTasks).toEqual([]);
    });

    it('should require name field', async () => {
      const userData = {
        email: 'test@example.com',
        password: 'password123',
      };

      expect(() => new User(userData)).toThrow();
    });

    it('should require email field', async () => {
      const userData = {
        name: 'Test User',
        password: 'password123',
      };

      expect(() => new User(userData)).toThrow();
    });

    it('should require password field', async () => {
      const userData = {
        name: 'Test User',
        email: 'test@example.com',
      };

      expect(() => new User(userData)).toThrow();
    });

    it('should enforce unique email constraint', async () => {
      const email = 'duplicate@example.com';
      const userData = {
        name: 'User 1',
        email,
        password: 'password123',
      };

      // Test that we can find a user by email
      const existingUser = {
        _id: 'user1',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };
      
      mockCouchdbService.findUserByEmail.mockResolvedValue(existingUser);

      const user = await User.findOne({ email });
      expect(user).toBeDefined();
      expect(user.email).toBe(email);
    });
  });

  describe('Default Values', () => {
    it('should set default values correctly', async () => {
      const userData = {
        name: 'Default Test',
        email: 'default@example.com',
        password: 'password123',
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);

      expect(user.isPremium).toBe(false);
      expect(user.points).toBe(0);
      expect(user.adoptedStreets).toEqual([]);
      expect(user.completedTasks).toEqual([]);
      expect(user.posts).toEqual([]);
      expect(user.events).toEqual([]);
    });
  });

  describe('Relationships', () => {
    it('should store adopted streets references', async () => {
      const streetId = 'street_123';
      const userData = {
        name: 'Test User',
        email: 'test@example.com',
        password: 'password123',
        adoptedStreets: [streetId],
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        points: 0,
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 1,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);

      expect(user.adoptedStreets).toHaveLength(1);
      expect(user.adoptedStreets[0]).toBe(streetId);
    });

    it('should store completed tasks references', async () => {
      const taskId = 'task_123';
      const userData = {
        name: 'Test User',
        email: 'test@example.com',
        password: 'password123',
        completedTasks: [taskId],
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 1,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);

      expect(user.completedTasks).toHaveLength(1);
      expect(user.completedTasks[0]).toBe(taskId);
    });

    it('should store multiple posts references', async () => {
      const postId1 = 'post_123';
      const postId2 = 'post_456';
      const userData = {
        name: 'Test User',
        email: 'test@example.com',
        password: 'password123',
        posts: [postId1, postId2],
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 2,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);

      expect(user.posts).toHaveLength(2);
      expect(user.posts[0]).toBe(postId1);
      expect(user.posts[1]).toBe(postId2);
    });
  });

  describe('Timestamps', () => {
    it('should automatically set createdAt and updatedAt', async () => {
      const userData = {
        name: 'Test User',
        email: 'timestamp@example.com',
        password: 'password123',
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);

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

  describe('Password Management', () => {
    it('should hash password on creation', async () => {
      const userData = {
        name: 'Test User',
        email: 'hash@example.com',
        password: 'password123',
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        password: '$2a$10$hashedpassword',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);
      expect(user.password).toMatch(/^\$2[aby]\$\d+\$/); // bcrypt hash pattern
    });

    it('should compare passwords correctly', async () => {
      const userData = {
        name: 'Test User',
        email: 'compare@example.com',
        password: 'password123',
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        password: '$2a$10$hashedpassword',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);
      
      // Mock bcrypt.compare
      const bcrypt = require('bcryptjs');
      bcrypt.compare = jest.fn().mockResolvedValue(true);

      const isMatch = await user.comparePassword('password123');
      expect(isMatch).toBe(true);
    });
  });

  describe('Premium Status', () => {
    it('should allow setting premium status', async () => {
      const userData = {
        name: 'Premium User',
        email: 'premium@example.com',
        password: 'password123',
        isPremium: true,
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);
      expect(user.isPremium).toBe(true);
    });

    it('should allow toggling premium status', async () => {
      const userData = {
        name: 'Test User',
        email: 'toggle@example.com',
        password: 'password123',
        isPremium: false,
      };

      const mockUser = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.findUserById.mockResolvedValue(mockUser);
      mockCouchdbService.updateDocument.mockResolvedValue({ ...mockUser, isPremium: true, _rev: '2-def' });

      const user = await User.findById('user_123');
      user.isPremium = true;
      await user.save();

      expect(user.isPremium).toBe(true);
    });
  });

  describe('Points Management', () => {
    it('should allow incrementing points', async () => {
      const userData = {
        name: 'Test User',
        email: 'points@example.com',
        password: 'password123',
        points: 100,
      };

      const mockUser = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.findUserById.mockResolvedValue(mockUser);
      mockCouchdbService.updateDocument.mockResolvedValue({ ...mockUser, points: 150, _rev: '2-def' });

      const user = await User.findById('user_123');
      user.points += 50;
      await user.save();

      expect(user.points).toBe(150);
    });

    it('should allow decrementing points', async () => {
      const userData = {
        name: 'Test User',
        email: 'deduct@example.com',
        password: 'password123',
        points: 100,
      };

      const mockUser = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.findUserById.mockResolvedValue(mockUser);
      mockCouchdbService.updateDocument.mockResolvedValue({ ...mockUser, points: 75, _rev: '2-def' });

      const user = await User.findById('user_123');
      user.points -= 25;
      await user.save();

      expect(user.points).toBe(75);
    });
  });

  describe('Profile Picture', () => {
    it('should store profile picture URL', async () => {
      const userData = {
        name: 'Test User',
        email: 'pic@example.com',
        password: 'password123',
        profilePicture: 'https://example.com/pic.jpg',
        cloudinaryPublicId: 'user_123',
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);

      expect(user.profilePicture).toBe('https://example.com/pic.jpg');
      expect(user.cloudinaryPublicId).toBe('user_123');
    });
  });

  describe('Static Methods', () => {
    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should find user by email', async () => {
      const mockUser = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        email: 'test@example.com',
        name: 'Test User',
        password: 'password123',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.findUserByEmail.mockResolvedValue(mockUser);

      const user = await User.findOne({ email: 'test@example.com' });
      expect(user).toBeDefined();
      expect(user.email).toBe('test@example.com');
    });

    it('should find user by ID', async () => {
      const mockUser = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        email: 'test@example.com',
        name: 'Test User',
        password: 'password123',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.findUserById.mockResolvedValue(mockUser);

      const user = await User.findById('user_123');
      expect(user).toBeDefined();
      expect(user._id).toBe('user_123');
    });

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

      const user = await User.findById('nonexistent');
      expect(user).toBeNull();
    });
  });

  describe('Helper Methods', () => {
    it('should return safe object without password', async () => {
      const userData = {
        name: 'Test User',
        email: 'safe@example.com',
        password: 'password123',
      };

      const mockCreated = {
        _id: 'user_123',
        _rev: '1-abc',
        type: 'user',
        ...userData,
        password: '$2a$10$hashedpassword',
        isPremium: false,
        points: 0,
        adoptedStreets: [],
        completedTasks: [],
        posts: [],
        events: [],
        earnedBadges: [],
        stats: {
          streetsAdopted: 0,
          tasksCompleted: 0,
          postsCreated: 0,
          eventsParticipated: 0,
          badgesEarned: 0
        },
        createdAt: '2023-01-01T00:00:00.000Z',
        updatedAt: '2023-01-01T00:00:00.000Z'
      };

      mockCouchdbService.createDocument.mockResolvedValue(mockCreated);

      const user = await User.create(userData);
      const safeUser = user.toSafeObject();

      expect(safeUser.password).toBeUndefined();
      expect(safeUser.name).toBe(userData.name);
      expect(safeUser.email).toBe(userData.email);
    });
  });
});
