import { describe, expect, it } from 'vitest';
import { CacheService } from '../application/cache-service.js';
import { InMemoryCacheAdapter } from '../infrastructure/in-memory-cache-adapter.js';

describe('CacheService', () => {
  it('records key, TTL, invalidation and source of truth per entry', () => {
    const service = new CacheService(new InMemoryCacheAdapter());
    service.registerContract({
      name: 'product',
      keyPattern: 'product:{slug}',
      ttlSeconds: 300,
      sourceOfTruth: 'catalog_products',
      invalidation: 'ProductUpdated',
    });
    const contracts = service.listContracts();
    expect(contracts).toEqual([
      {
        name: 'product',
        keyPattern: 'product:{slug}',
        ttlSeconds: 300,
        sourceOfTruth: 'catalog_products',
        invalidation: 'ProductUpdated',
      },
    ]);
  });

  it('returns from cache on second read and increments hits', async () => {
    const service = new CacheService(new InMemoryCacheAdapter());
    service.registerContract({
      name: 'product',
      keyPattern: 'product:{slug}',
      ttlSeconds: 60,
      sourceOfTruth: 'catalog_products',
      invalidation: 'ProductUpdated',
    });
    let loaderCalls = 0;
    const loader = async () => {
      loaderCalls += 1;
      return { name: 'X' };
    };
    const a = await service.read<{ name: string }>('product', 'product:x', loader);
    const b = await service.read<{ name: string }>('product', 'product:x', loader);
    expect(a).toEqual({ name: 'X' });
    expect(b).toEqual({ name: 'X' });
    expect(loaderCalls).toBe(1);
    expect(service.metrics()).toMatchObject({ hits: 1, misses: 1 });
  });

  it('falls back to loader when adapter is down and still records metrics', async () => {
    const adapter: import('../domain/ports.js').CacheAdapter = {
      get: async () => undefined,
      set: async () => {
        throw new Error('redis down');
      },
      invalidate: async () => {
        throw new Error('redis down');
      },
    };
    const service = new CacheService(adapter);
    service.registerContract({
      name: 'product',
      keyPattern: 'product:{slug}',
      ttlSeconds: 60,
      sourceOfTruth: 'catalog_products',
      invalidation: 'ProductUpdated',
    });
    const value = await service.read<{ name: string }>('product', 'product:x', async () => ({
      name: 'X',
    }));
    expect(value).toEqual({ name: 'X' });
    expect(service.metrics().misses).toBe(1);
  });
});
