import { type ExecutionContext, type CallHandler } from '@nestjs/common';
import { of } from 'rxjs';
import { lastValueFrom } from 'rxjs';
import { cacheMetaStorage } from '@modules/shared';
import { CacheMetaInterceptor, type WithCacheMeta } from '../interceptors/cache-meta.interceptor';

function makeContext(): ExecutionContext {
  return {} as ExecutionContext;
}

function makeHandler<T>(value: T): CallHandler {
  return { handle: () => of(value) };
}

describe('CacheMetaInterceptor — analytics endpoints', () => {
  let interceptor: CacheMetaInterceptor;

  beforeEach(() => {
    interceptor = new CacheMetaInterceptor();
  });

  it('market-report: wraps payload with cacheMeta.source=fresh when no cache was hit', async () => {
    const payload = { city: 'Hồ Chí Minh', period: '2026-Q1', districts: [] };
    const result = await lastValueFrom(
      interceptor.intercept(makeContext(), makeHandler(payload)),
    ) as WithCacheMeta<typeof payload>;

    expect(result.data).toEqual(payload);
    expect(result.cacheMeta).toMatchObject({
      source: 'fresh',
    });
  });

  it('price-trend: wraps payload with cacheMeta.source=fresh when no cache was hit', async () => {
    const payload = { district: 'Quận 1', city: 'Hồ Chí Minh', propertyType: 'APARTMENT', trend: [] };
    const result = await lastValueFrom(
      interceptor.intercept(makeContext(), makeHandler(payload)),
    ) as WithCacheMeta<typeof payload>;

    expect(result.data).toEqual(payload);
    expect(result.cacheMeta).toMatchObject({
      source: 'fresh',
    });
  });

  it('heatmap: wraps payload with cacheMeta.source=fresh when no cache was hit', async () => {
    const payload = { city: 'Hồ Chí Minh', period: '2026-Q1', dataPoints: [] };
    const result = await lastValueFrom(
      interceptor.intercept(makeContext(), makeHandler(payload)),
    ) as WithCacheMeta<typeof payload>;

    expect(result.data).toEqual(payload);
    expect(result.cacheMeta).toMatchObject({
      source: 'fresh',
    });
  });

  it('surfaces cache-hit meta when store is populated by CacheService', async () => {
    const cachedAt = '2026-04-21T10:00:00.000Z';
    const nextRefreshAt = '2026-04-21T10:15:00.000Z';
    const payload = { city: 'Hồ Chí Minh', period: '2026-Q1', districts: [] };

    // Simulate CacheService populating the store during handler execution
    const handler: CallHandler = {
      handle: () => {
        const store = cacheMetaStorage.getStore();
        if (store) {
          store.meta = { cachedAt, nextRefreshAt, source: 'cache' };
        }
        return of(payload);
      },
    };

    const result = await lastValueFrom(
      interceptor.intercept(makeContext(), handler),
    ) as WithCacheMeta<typeof payload>;

    expect(result.cacheMeta).toEqual({ cachedAt, nextRefreshAt, source: 'cache' });
    expect(result.data).toEqual(payload);
  });

  it('provides null cachedAt/nextRefreshAt for fresh responses', async () => {
    const result = await lastValueFrom(
      interceptor.intercept(makeContext(), makeHandler({ ok: true })),
    ) as WithCacheMeta<unknown>;

    expect(result.cacheMeta.cachedAt).toBeNull();
    expect(result.cacheMeta.nextRefreshAt).toBeNull();
  });

  it('does not leak meta between concurrent requests (ALS isolation)', async () => {
    const cachedAt = '2026-04-21T08:00:00.000Z';

    const handler1: CallHandler = {
      handle: () => {
        const store = cacheMetaStorage.getStore();
        if (store) store.meta = { cachedAt, nextRefreshAt: cachedAt, source: 'cache' };
        return of({ req: 1 });
      },
    };
    const handler2: CallHandler = {
      handle: () => of({ req: 2 }),
    };

    const [r1, r2] = await Promise.all([
      lastValueFrom(interceptor.intercept(makeContext(), handler1)),
      lastValueFrom(interceptor.intercept(makeContext(), handler2)),
    ]) as [WithCacheMeta<unknown>, WithCacheMeta<unknown>];

    expect(r1.cacheMeta.source).toBe('cache');
    expect(r2.cacheMeta.source).toBe('fresh');
  });
});
