import { describe, it, expect, vi, beforeEach } from 'vitest';

const { mockFind, mockFindOne, mockFindOneAndUpdate, mockSave } = vi.hoisted(() => ({
  mockFind: vi.fn(),
  mockFindOne: vi.fn(),
  mockFindOneAndUpdate: vi.fn(),
  mockSave: vi.fn(),
}));

vi.mock('../../../src/schemas/regimen.schema.js', () => {
  const chain = () => ({
    sort: vi.fn().mockReturnThis(),
    limit: vi.fn().mockReturnThis(),
    lean: vi.fn().mockReturnThis(),
    exec: mockFind,
  });

  const findOneChain = () => ({
    lean: vi.fn().mockReturnThis(),
    exec: mockFindOne,
  });

  const updateChain = () => ({
    exec: mockFindOneAndUpdate,
  });

  class FakeModel {
    data: unknown;
    constructor(data: unknown) {
      this.data = data;
    }
    save = mockSave;
    toObject() {
      return this.data;
    }
    static find = vi.fn(() => chain());
    static findOne = vi.fn(() => findOneChain());
    static findOneAndUpdate = vi.fn(() => updateChain());
  }

  return { RegimenModel: FakeModel };
});

import { RegimensRepository } from '../../../src/modules/regimens/regimens.repository.js';

describe(RegimensRepository.name, () => {
  let repo: RegimensRepository;

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

  describe('findByHousehold', () => {
    it('returns paginated items', async () => {
      const items = [{ _id: 'reg-1', name: 'Morning' }];
      mockFind.mockResolvedValue(items);

      const result = await repo.findByHousehold('hh1', 'user-1', { limit: 20 });

      expect(result.data).toEqual(items);
      expect(result.pagination.hasMore).toBe(false);
    });

    it('handles cursor-based pagination', async () => {
      const items = [{ _id: 'reg-2', name: 'Evening' }];
      mockFind.mockResolvedValue(items);

      const cursor = Buffer.from('reg-1').toString('base64');
      const result = await repo.findByHousehold('hh1', 'user-1', { cursor, limit: 20 });

      expect(result.data).toEqual(items);
      expect(result.pagination.hasMore).toBe(false);
    });

    it('sets hasMore when more items exist', async () => {
      const items = Array.from({ length: 3 }, (_, i) => ({ _id: `reg-${i}`, name: `Reg ${i}` }));
      mockFind.mockResolvedValue(items);

      const result = await repo.findByHousehold('hh1', 'user-1', { limit: 2 });

      expect(result.data).toHaveLength(2);
      expect(result.pagination.hasMore).toBe(true);
      expect(result.pagination.cursor).toBeTruthy();
    });

    it('returns null cursor when no data', async () => {
      mockFind.mockResolvedValue([]);

      const result = await repo.findByHousehold('hh1', 'user-1', { limit: 20 });

      expect(result.pagination.cursor).toBeNull();
      expect(result.pagination.hasMore).toBe(false);
    });

    it('filters by isActive', async () => {
      mockFind.mockResolvedValue([]);

      await repo.findByHousehold('hh1', 'user-1', { isActive: true, limit: 20 });

      expect(mockFind).toHaveBeenCalled();
    });

    it('does not add isActive filter when undefined', async () => {
      mockFind.mockResolvedValue([]);

      await repo.findByHousehold('hh1', 'user-1', { limit: 20 });

      expect(mockFind).toHaveBeenCalled();
    });

    it('returns cursor only when hasMore is true', async () => {
      const items = [{ _id: 'reg-1', name: 'Morning' }];
      mockFind.mockResolvedValue(items);

      const result = await repo.findByHousehold('hh1', 'user-1', { limit: 20 });

      expect(result.pagination.cursor).toBeNull();
      expect(result.pagination.hasMore).toBe(false);
    });
  });

  describe('findById', () => {
    it('returns regimen by id, householdId, and userId', async () => {
      const regimen = { _id: 'reg-1', householdId: 'hh1', userId: 'user-1', name: 'Morning' };
      mockFindOne.mockResolvedValue(regimen);

      const result = await repo.findById('reg-1', 'hh1', 'user-1');

      expect(result).toEqual(regimen);
    });

    it('returns null when not found', async () => {
      mockFindOne.mockResolvedValue(null);

      const result = await repo.findById('reg-missing', 'hh1', 'user-1');

      expect(result).toBeNull();
    });
  });

  describe('findActiveByUser', () => {
    it('returns active regimens for user', async () => {
      const regimens = [
        { _id: 'reg-1', isActive: true },
        { _id: 'reg-2', isActive: true },
      ];
      mockFind.mockResolvedValue(regimens);

      const result = await repo.findActiveByUser('hh1', 'user-1');

      expect(result).toEqual(regimens);
    });

    it('returns empty array when no active regimens', async () => {
      mockFind.mockResolvedValue([]);

      const result = await repo.findActiveByUser('hh1', 'user-1');

      expect(result).toEqual([]);
    });
  });

  describe('create', () => {
    it('creates and returns regimen', async () => {
      const data = {
        name: 'Morning Routine',
        isActive: true,
        medications: [
          {
            medicineId: 'med-1',
            medicineName: 'Metformin',
            medicineStrength: 500,
            medicineStrengthUnit: 'mg',
            medicineForm: 'tablet',
            dosage: 1,
            dosageUnit: 'tablet',
            frequency: 'daily',
          },
        ],
      };
      mockSave.mockImplementation(function (this: { toObject: () => unknown }) {
        return Promise.resolve(this);
      });

      const result = await repo.create(data, 'hh1', 'user-1', 'user-1');

      expect(result).toBeTruthy();
      expect(mockSave).toHaveBeenCalled();
    });
  });

  describe('update', () => {
    it('updates and returns regimen', async () => {
      const updated = { _id: 'reg-1', name: 'Updated' };
      mockFindOneAndUpdate.mockResolvedValue(updated);

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

      expect(result).toEqual(updated);
    });

    it('returns null when regimen not found', async () => {
      mockFindOneAndUpdate.mockResolvedValue(null);

      const result = await repo.update('reg-missing', 'hh1', 'user-1', { name: 'Updated' });

      expect(result).toBeNull();
    });
  });

  describe('softDelete', () => {
    it('soft deletes and returns regimen', async () => {
      const deleted = { _id: 'reg-1', isDeleted: true };
      mockFindOneAndUpdate.mockResolvedValue(deleted);

      const result = await repo.softDelete('reg-1', 'hh1', 'user-1');

      expect(result).toEqual(deleted);
    });

    it('returns null when regimen not found', async () => {
      mockFindOneAndUpdate.mockResolvedValue(null);

      const result = await repo.softDelete('reg-missing', 'hh1', 'user-1');

      expect(result).toBeNull();
    });
  });
});
