import { Inject } from "@nestjs/common";
import { Cache } from "cache-manager";

import { CACHE_MANAGER } from "@nestjs/cache-manager";
import { cacheConfigOption, getCacheManager } from "./cacheable.helpers";
import { CacheableOptions } from "./cacheable.interfaces";
type CacheKeyFn = (args: any[]) => string;

interface CacheOptions {
  ttl?: number; // Thời gian sống của cache (TTL)
  refreshThreshold?: number; // Tỷ lệ để tự động làm mới cache (default 0.9)
  key?: string | CacheKeyFn; // Key có thể là chuỗi hoặc hàm tạo key
  prefix?: string; // Tiền tố để xác định key cần xóa
  type?: "bust" | "delete"; // Các hành động như bust (làm mới cache) hoặc delete (xóa cache)
  concatArgs?: boolean; // Chỉ định có kết hợp các tham số hàm vào key không
}
const log = (m: any) => {
  if (cacheConfigOption.isDev) {
    console.log(m);
  }
};
export function Cacheable(options?: CacheOptions): MethodDecorator {
  return (
    target: any,
    propertyKey: string | symbol,
    descriptor: PropertyDescriptor
  ) => {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const {
        ttl,
        refreshThreshold,
        key,
        prefix,
        type,
        concatArgs = true,
      } = {
        ...cacheConfigOption,
        ...options,
      };

      const cacheManager: Cache = getCacheManager();
      const initKey = `${
        target.constructor.name
      }-${propertyKey.toString()}-${args
        .map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : arg))
        .join("-")}`;
      let cacheKey: string = initKey;
      log({
        ttl,
        refreshThreshold,
        key,
        prefix,
        type,
        concatArgs,
      });
      if (typeof key === "string") {
        cacheKey = key;
        if (concatArgs) {
          cacheKey = `${key}-${initKey}`;
        }
      } else if (typeof key === "function") {
        cacheKey = key(args);
      }
      if (!cacheKey) {
        throw new Error("cacheKey can not be empty!");
      }
      log("key " + cacheKey);

      const currentTime = Date.now();

      // Nếu `type='delete'`, xóa cache
      if (type === "delete") {
        log(`Deleting cache for key: ${cacheKey}`);
        await cacheManager.del(cacheKey); // Xóa key hiện tại

        // Nếu có prefix, xóa tất cả các keys có prefix
        if (prefix) {
          const allKeys = await cacheManager.store.keys(); // Lấy tất cả các keys trong cache
          const prefixKeys = allKeys.filter((key) => key.startsWith(prefix));
          for (const key of prefixKeys) {
            await cacheManager.del(key); // Xóa các keys có prefix
            log(`Deleted cache for key with prefix: ${key}`);
          }
        }

        // Gọi lại hàm gốc để tạo cache mới
        const newValue = await originalMethod.apply(this, args);
        await cacheManager.set(
          cacheKey,
          { value: newValue, timestamp: currentTime },
          ttl
        );
        return newValue;
      }

      // Nếu `type='bust'`, làm mới cache
      if (type === "bust") {
        log(`Force refresh cache for key: ${cacheKey}`);
        const newValue = await originalMethod.apply(this, args);
        await cacheManager.set(
          cacheKey,
          { value: newValue, timestamp: currentTime },
          ttl
        );
        return newValue;
      }

      // Kiểm tra cache
      const cachedData = await cacheManager.get<{
        value: any;
        timestamp: number;
      }>(cacheKey);
      if (cachedData) {
        const { value, timestamp } = cachedData;
        const elapsed = (currentTime - timestamp) / 1000; // Thời gian đã trôi qua (giây)
        const throttleTime = ttl * (refreshThreshold || elapsed);

        if (elapsed >= throttleTime) {
          log(`Auto-refresh cache for key: ${cacheKey}`);

          const newValue = await originalMethod.apply(this, args);
          await cacheManager.set(
            cacheKey,
            { value: newValue, timestamp: currentTime },
            ttl
          );
          return newValue;
        }

        log(`Cache hit for key: ${cacheKey}`);
        return value;
      }

      log(`Cache miss for key: ${cacheKey}`);
      const result = await originalMethod.apply(this, args);
      await cacheManager.set(
        cacheKey,
        { value: result, timestamp: currentTime },
        ttl
      );
      return result;
    };

    return descriptor;
  };
}
