import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Reflector } from '@nestjs/core';
import { Observable, from, of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import {
  CACHE_RESPONSE_KEY,
  CacheResponseOptions,
} from 'src/common/decorators/cache-response.decorator';
import { PUBLIC_CACHE_TTL } from 'src/common/constants/cache-keys.constant';
import { CacheService } from 'src/modules/utils/cache.service';

@Injectable()
export class HttpCacheInterceptor implements NestInterceptor {
  constructor(
    private readonly cacheService: CacheService,
    private readonly reflector: Reflector,
    private readonly configService: ConfigService,
  ) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    const options = this.reflector.get<CacheResponseOptions>(
      CACHE_RESPONSE_KEY,
      context.getHandler(),
    );
    if (!options) {
      return next.handle();
    }

    const request = context.switchToHttp().getRequest<{ params: Record<string, string> }>();
    const key = this.buildCacheKey(options, request.params);
    const ttl =
      options.ttl ??
      this.configService.get<number>('PUBLIC_CACHE_TTL', PUBLIC_CACHE_TTL);

    return from(this.cacheService.get<unknown>(key)).pipe(
      mergeMap(cached => {
        if (cached !== undefined && cached !== null) {
          return of(cached);
        }

        return next.handle().pipe(
          mergeMap(async data => {
            const serialized = JSON.parse(JSON.stringify(data));
            await this.cacheService.set(key, serialized, ttl);
            return data;
          }),
        );
      }),
    );
  }

  private buildCacheKey(
    options: CacheResponseOptions,
    params: Record<string, string>,
  ): string {
    const paramValues = options.params?.length
      ? options.params.map(name => params[name]).filter(Boolean)
      : Object.values(params);

    return [options.keyPrefix, ...paramValues].join(':');
  }
}
