import { describe, it, expect, vi, beforeEach } from 'vitest';
import { FreshnessRulesService } from '../../../src/modules/freshness-rules/freshness-rules.service.js';
import { NotFoundError, BadRequestError } from '../../../src/common/errors.js';
import { FreshnessRuleSource } from '@meshitrack/shared';

const mockRepo = {
  findByHousehold: vi.fn(),
  findById: vi.fn(),
  findApplicableRule: vi.fn(),
  create: vi.fn(),
  update: vi.fn(),
  delete: vi.fn(),
};

describe(FreshnessRulesService.name, () => {
  let service: FreshnessRulesService;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new FreshnessRulesService({
      freshnessRulesRepository: mockRepo as never,
    });
  });

  describe('list', () => {
    it('delegates to repository', async () => {
      const expected = { data: [], pagination: { cursor: null, hasMore: false } };
      mockRepo.findByHousehold.mockResolvedValue(expected);
      const result = await service.list('hh1', { limit: 50 });
      expect(result).toEqual(expected);
    });
  });

  describe('create', () => {
    it('creates household rule', async () => {
      const data = {
        category: 'dairy' as never,
        storageLocation: 'fridge' as never,
        shelfLifeDays: 14,
        openedLifeDays: 7,
        spoilageSignsToCheck: [],
      };
      mockRepo.create.mockResolvedValue({ ...data, _id: 'r1', householdId: 'hh1' });

      const result = await service.create(data, 'hh1');
      expect(mockRepo.create).toHaveBeenCalledWith({
        ...data,
        householdId: 'hh1',
        source: FreshnessRuleSource.HOUSEHOLD,
      });
      expect(result).toBeDefined();
    });
  });

  describe('update', () => {
    it('updates household rule', async () => {
      mockRepo.findById.mockResolvedValue({
        _id: 'r1',
        householdId: 'hh1',
        source: FreshnessRuleSource.HOUSEHOLD,
      });
      mockRepo.update.mockResolvedValue({ _id: 'r1', shelfLifeDays: 10 });

      const result = await service.update('r1', 'hh1', { shelfLifeDays: 10 });
      expect(result).toBeDefined();
    });

    it('throws NotFoundError when rule not found', async () => {
      mockRepo.findById.mockResolvedValue(null);
      await expect(service.update('missing', 'hh1', {})).rejects.toThrow(NotFoundError);
    });

    it('throws BadRequestError for system rules', async () => {
      mockRepo.findById.mockResolvedValue({
        _id: 'r1',
        householdId: null,
        source: FreshnessRuleSource.SYSTEM,
      });
      await expect(service.update('r1', 'hh1', {})).rejects.toThrow(BadRequestError);
    });

    it('throws NotFoundError for another household rule', async () => {
      mockRepo.findById.mockResolvedValue({
        _id: 'r1',
        householdId: 'other-hh',
        source: FreshnessRuleSource.HOUSEHOLD,
      });
      await expect(service.update('r1', 'hh1', {})).rejects.toThrow(NotFoundError);
    });

    it('throws NotFoundError when update returns null', async () => {
      mockRepo.findById.mockResolvedValue({
        _id: 'r1',
        householdId: 'hh1',
        source: FreshnessRuleSource.HOUSEHOLD,
      });
      mockRepo.update.mockResolvedValue(null);
      await expect(service.update('r1', 'hh1', {})).rejects.toThrow(NotFoundError);
    });
  });

  describe('delete', () => {
    it('deletes household rule', async () => {
      mockRepo.findById.mockResolvedValue({
        _id: 'r1',
        householdId: 'hh1',
        source: FreshnessRuleSource.HOUSEHOLD,
      });
      mockRepo.delete.mockResolvedValue({ _id: 'r1' });
      await service.delete('r1', 'hh1');
      expect(mockRepo.delete).toHaveBeenCalledWith('r1', 'hh1');
    });

    it('throws NotFoundError when rule not found', async () => {
      mockRepo.findById.mockResolvedValue(null);
      await expect(service.delete('missing', 'hh1')).rejects.toThrow(NotFoundError);
    });

    it('throws BadRequestError for system rules', async () => {
      mockRepo.findById.mockResolvedValue({
        _id: 'r1',
        householdId: null,
        source: FreshnessRuleSource.SYSTEM,
      });
      await expect(service.delete('r1', 'hh1')).rejects.toThrow(BadRequestError);
    });

    it('throws NotFoundError for another household rule', async () => {
      mockRepo.findById.mockResolvedValue({
        _id: 'r1',
        householdId: 'other-hh',
        source: FreshnessRuleSource.HOUSEHOLD,
      });
      await expect(service.delete('r1', 'hh1')).rejects.toThrow(NotFoundError);
    });
  });
});
