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

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

vi.mock('../../../src/schemas/freshness-rule.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 findByIdChain = () => ({
    lean: vi.fn().mockReturnThis(),
    exec: mockFindById,
  });

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

  const deleteChain = () => ({
    exec: mockFindOneAndDelete,
  });

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

  return { FreshnessRuleModel: FakeModel };
});

import { FreshnessRulesRepository } from '../../../src/modules/freshness-rules/freshness-rules.repository.js';

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

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

  describe('findByHousehold', () => {
    it('returns paginated results', async () => {
      mockFind.mockResolvedValue([{ _id: { toString: () => 'id1' } }]);
      const result = await repo.findByHousehold('hh1', { limit: 50 });
      expect(result.data).toHaveLength(1);
      expect(result.pagination.hasMore).toBe(false);
    });

    it('applies category filter', async () => {
      mockFind.mockResolvedValue([]);
      await repo.findByHousehold('hh1', { category: 'dairy', limit: 50 });
      expect(mockFind).toHaveBeenCalled();
    });

    it('applies storageLocation filter', async () => {
      mockFind.mockResolvedValue([]);
      await repo.findByHousehold('hh1', { storageLocation: 'fridge', limit: 50 });
      expect(mockFind).toHaveBeenCalled();
    });

    it('applies cursor', async () => {
      const cursor = Buffer.from('abc').toString('base64');
      mockFind.mockResolvedValue([]);
      await repo.findByHousehold('hh1', { cursor, limit: 50 });
      expect(mockFind).toHaveBeenCalled();
    });
  });

  describe('findById', () => {
    it('returns rule', async () => {
      mockFindById.mockResolvedValue({ _id: 'id1' });
      const result = await repo.findById('id1');
      expect(result).toEqual({ _id: 'id1' });
    });
  });

  describe('findApplicableRule', () => {
    it('returns household rule when available', async () => {
      const rule = { _id: 'r1', householdId: 'hh1' };
      mockFindOne.mockResolvedValueOnce(rule);
      const result = await repo.findApplicableRule('hh1', 'dairy', 'fridge');
      expect(result).toEqual(rule);
    });

    it('falls back to system rule', async () => {
      const systemRule = { _id: 'r2', householdId: null };
      mockFindOne.mockResolvedValueOnce(null).mockResolvedValueOnce(systemRule);
      const result = await repo.findApplicableRule('hh1', 'dairy', 'fridge');
      expect(result).toEqual(systemRule);
    });

    it('returns null when no rule found', async () => {
      mockFindOne.mockResolvedValue(null);
      const result = await repo.findApplicableRule('hh1', 'dairy', 'fridge');
      expect(result).toBeNull();
    });
  });

  describe('create', () => {
    it('saves and returns rule', async () => {
      const data = {
        category: 'dairy',
        storageLocation: 'fridge',
        shelfLifeDays: 14,
        openedLifeDays: 7,
      };
      const result = await repo.create(data);
      expect(mockSave).toHaveBeenCalled();
      expect(result).toEqual(data);
    });
  });

  describe('update', () => {
    it('updates and returns rule', async () => {
      mockFindOneAndUpdate.mockResolvedValue({ _id: 'id1' });
      const result = await repo.update('id1', 'hh1', { shelfLifeDays: 10 });
      expect(result).toEqual({ _id: 'id1' });
    });
  });

  describe('delete', () => {
    it('deletes rule', async () => {
      mockFindOneAndDelete.mockResolvedValue({ _id: 'id1' });
      await repo.delete('id1', 'hh1');
      expect(mockFindOneAndDelete).toHaveBeenCalled();
    });
  });
});
