import {
  Injectable,
  type CallHandler,
  type ExecutionContext,
  type NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { cacheMetaStorage, type CacheMeta } from '@modules/shared';

/**
 * Shape appended to every `/analytics/*` response.
 */
export interface WithCacheMeta<T> {
  data: T;
  cacheMeta: CacheMeta;
}

/**
 * NestJS interceptor that:
 *  1. Creates an AsyncLocalStorage context for the request so CacheService
 *     can populate per-request cache metadata.
 *  2. After the handler resolves, wraps the response payload with a `cacheMeta`
 *     field describing freshness: `{ cachedAt, nextRefreshAt, source }`.
 *
 * Apply at controller class or individual method level:
 * ```ts
 * @UseInterceptors(CacheMetaInterceptor)
 * @Controller('analytics')
 * export class AnalyticsController { ... }
 * ```
 *
 * Responses are transformed from `T` to `{ data: T; cacheMeta: CacheMeta }`.
 * When CacheService was not called during the request (e.g. command endpoints),
 * `cacheMeta` defaults to `{ cachedAt: null, nextRefreshAt: null, source: 'fresh' }`.
 */
@Injectable()
export class CacheMetaInterceptor implements NestInterceptor {
  intercept(_context: ExecutionContext, next: CallHandler): Observable<WithCacheMeta<unknown>> {
    const store = { meta: null as CacheMeta | null };

    return new Observable((subscriber) => {
      cacheMetaStorage.run(store, () => {
        next
          .handle()
          .pipe(
            map((data: unknown) => {
              const cacheMeta: CacheMeta = store.meta ?? {
                cachedAt: null,
                nextRefreshAt: null,
                source: 'fresh',
              };
              return { data, cacheMeta };
            }),
          )
          .subscribe(subscriber);
      });
    });
  }
}
