import { describe, it, expect, vi, beforeEach } from 'vitest';
import { NutritionTargetRepository } from '../../../src/modules/nutrition-targets/nutrition-target.repository.js';

const { mockSave, MockTargetModel } = vi.hoisted(() => {
  const mockSave = vi.fn();
  function MockModel(this: { save: typeof mockSave }, data: unknown) {
    Object.assign(this, data);
    this.save = mockSave;
  }
  Object.assign(MockModel, {
    findOne: vi.fn(),
    find: vi.fn(),
    findOneAndUpdate: vi.fn(),
    updateMany: vi.fn(),
  });
  return { mockSave, MockTargetModel: MockModel };
});

vi.mock('../../../src/schemas/nutrition-target.schema.js', () => ({
  NutritionTargetModel: MockTargetModel,
}));

const { NutritionTargetModel } = await import('../../../src/schemas/nutrition-target.schema.js');

function makeChain(result: unknown = null) {
  return {
    sort: vi.fn().mockReturnThis(),
    lean: vi.fn().mockReturnThis(),
    exec: vi.fn().mockResolvedValue(result),
  };
}

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

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

  describe('findByUser', () => {
    it('queries by userId, householdId, and isActive: true', async () => {
      const mockTarget = { _id: 't1', dailyCalories: 2000 };
      vi.mocked(NutritionTargetModel.findOne).mockReturnValue(makeChain(mockTarget) as never);

      const result = await repo.findByUser('user1', 'hh1');
      expect(NutritionTargetModel.findOne).toHaveBeenCalledWith({
        userId: 'user1',
        householdId: 'hh1',
        isActive: true,
      });
      expect(result).toEqual(mockTarget);
    });
  });

  describe('findAllByUser', () => {
    it('returns all targets sorted by newest first', async () => {
      const chain = makeChain([]);
      vi.mocked(NutritionTargetModel.find).mockReturnValue(chain as never);

      await repo.findAllByUser('user1', 'hh1');
      expect(NutritionTargetModel.find).toHaveBeenCalledWith({
        userId: 'user1',
        householdId: 'hh1',
      });
      expect(chain.sort).toHaveBeenCalledWith({ createdAt: -1 });
    });
  });

  describe('create', () => {
    it('saves and returns new document', async () => {
      const plainDoc = { _id: 'new-id', dailyCalories: 2000 };
      mockSave.mockResolvedValue({ toObject: () => plainDoc });

      const result = await repo.create({ dailyCalories: 2000 });
      expect(mockSave).toHaveBeenCalled();
      expect(result).toEqual(plainDoc);
    });
  });

  describe('deactivateAllForUser', () => {
    it('updates all active targets for the user to inactive', async () => {
      vi.mocked(NutritionTargetModel.updateMany).mockReturnValue({
        exec: vi.fn().mockResolvedValue({ modifiedCount: 1 }),
      } as never);

      await repo.deactivateAllForUser('user1', 'hh1');
      expect(NutritionTargetModel.updateMany).toHaveBeenCalledWith(
        { userId: 'user1', householdId: 'hh1', isActive: true },
        { $set: { isActive: false } },
      );
    });
  });

  describe('update', () => {
    it('updates specific target using findOneAndUpdate', async () => {
      const updatedDoc = { _id: 't1', dailyCalories: 2100 };
      vi.mocked(NutritionTargetModel.findOneAndUpdate).mockReturnValue(makeChain(updatedDoc) as never);

      const result = await repo.update('t1', 'user1', 'hh1', { dailyCalories: 2100 });
      expect(NutritionTargetModel.findOneAndUpdate).toHaveBeenCalledWith(
        { _id: 't1', userId: 'user1', householdId: 'hh1' },
        { $set: { dailyCalories: 2100 } },
        { new: true, lean: true }
      );
      expect(result).toEqual(updatedDoc);
    });
  });
});
