import { describe, it, expect, vi, beforeEach } from 'vitest';
import { HouseholdRole } from '@meshitrack/shared';

const { _mockLean, mockExec, mockFindById, mockFindOne, mockFindByIdAndUpdate, mockSave } =
  vi.hoisted(() => {
    const mockExec = vi.fn();
    const mockLean = vi.fn(() => ({ exec: mockExec }));
    return {
      mockExec,
      mockLean,
      mockFindById: vi.fn(() => ({ lean: mockLean })),
      mockFindOne: vi.fn(() => ({ lean: mockLean })),
      mockFindByIdAndUpdate: vi.fn(() => ({ exec: mockExec })),
      mockSave: vi.fn(),
    };
  });

vi.mock('../../../src/schemas/household.schema.js', () => {
  class MockHouseholdModel {
    _data: Record<string, unknown>;
    constructor(data: Record<string, unknown>) {
      this._data = data;
      Object.assign(this, data);
    }
    save() {
      mockSave();
      return Promise.resolve(this);
    }
    toObject() {
      return { _id: 'hh-new', ...this._data };
    }
    static findById = mockFindById;
    static findOne = mockFindOne;
    static findByIdAndUpdate = mockFindByIdAndUpdate;
  }
  return { HouseholdModel: MockHouseholdModel };
});

import { HouseholdsRepository } from '../../../src/modules/households/households.repository.js';

describe('HouseholdsRepository', () => {
  let repo: HouseholdsRepository;

  beforeEach(() => {
    vi.clearAllMocks();
    repo = new HouseholdsRepository();
  });

  describe('findById', () => {
    it('calls findById with lean', async () => {
      const household = { _id: 'hh1', name: 'Test' };
      mockExec.mockResolvedValue(household);

      const result = await repo.findById('hh1');

      expect(mockFindById).toHaveBeenCalledWith('hh1');
      expect(result).toEqual(household);
    });
  });

  describe('findByInviteCode', () => {
    it('calls findOne with inviteCode', async () => {
      const household = { _id: 'hh1', inviteCode: 'ABCD' };
      mockExec.mockResolvedValue(household);

      const result = await repo.findByInviteCode('ABCD');

      expect(mockFindOne).toHaveBeenCalledWith({ inviteCode: 'ABCD' });
      expect(result).toEqual(household);
    });
  });

  describe('create', () => {
    it('creates a household with owner as first member', async () => {
      mockSave.mockResolvedValue({});

      const result = await repo.create({ name: 'Test' }, 'owner-1', 'INVITE1');

      expect(mockSave).toHaveBeenCalled();
      expect(result).toMatchObject({ name: 'Test', ownerUserId: 'owner-1', inviteCode: 'INVITE1' });
    });
  });

  describe('update', () => {
    it('calls findByIdAndUpdate with $set', async () => {
      mockExec.mockResolvedValue({ _id: 'hh1', name: 'Updated' });

      const result = await repo.update('hh1', { name: 'Updated' });

      expect(mockFindByIdAndUpdate).toHaveBeenCalledWith(
        'hh1',
        { $set: { name: 'Updated' } },
        { new: true, lean: true },
      );
      expect(result).toEqual({ _id: 'hh1', name: 'Updated' });
    });
  });

  describe('addMember', () => {
    it('pushes a new member to the array', async () => {
      mockExec.mockResolvedValue({ _id: 'hh1', members: [] });

      await repo.addMember('hh1', 'user-2', HouseholdRole.MEMBER);

      expect(mockFindByIdAndUpdate).toHaveBeenCalledWith(
        'hh1',
        {
          $push: {
            members: expect.objectContaining({
              userId: 'user-2',
              role: HouseholdRole.MEMBER,
            }),
          },
        },
        { new: true, lean: true },
      );
    });
  });

  describe('updateInviteCode', () => {
    it('sets the new invite code', async () => {
      mockExec.mockResolvedValue({ _id: 'hh1', inviteCode: 'NEWCODE' });

      const result = await repo.updateInviteCode('hh1', 'NEWCODE');

      expect(mockFindByIdAndUpdate).toHaveBeenCalledWith(
        'hh1',
        { $set: { inviteCode: 'NEWCODE' } },
        { new: true, lean: true },
      );
      expect(result).toEqual({ _id: 'hh1', inviteCode: 'NEWCODE' });
    });
  });
});
