import { Injectable } from '@nestjs/common';
import { RedisService } from '../redis/redis.service';

type MemoryEntry = {
  value: string;
  expiresAt: number | null;
};

@Injectable()
export class AppCacheService {
  private readonly memory = new Map<string, MemoryEntry>();

  constructor(private readonly redisService: RedisService) {}

  async get<T>(key: string): Promise<T | null> {
    const redis = this.redisService.getClient();
    const fullKey = this.buildKey(key);

    if (redis) {
      const value = await redis.get(fullKey);
      if (!value) {
        return null;
      }
      return JSON.parse(value) as T;
    }

    const memoryEntry = this.memory.get(fullKey);
    if (!memoryEntry) {
      return null;
    }

    if (memoryEntry.expiresAt && memoryEntry.expiresAt <= Date.now()) {
      this.memory.delete(fullKey);
      return null;
    }

    return JSON.parse(memoryEntry.value) as T;
  }

  async set<T>(key: string, value: T, ttlSeconds?: number): Promise<void> {
    const redis = this.redisService.getClient();
    const fullKey = this.buildKey(key);
    const serialized = JSON.stringify(value);

    if (redis) {
      if (ttlSeconds && ttlSeconds > 0) {
        await redis.set(fullKey, serialized, 'EX', ttlSeconds);
        return;
      }

      await redis.set(fullKey, serialized);
      return;
    }

    const expiresAt = ttlSeconds && ttlSeconds > 0 ? Date.now() + ttlSeconds * 1000 : null;
    this.memory.set(fullKey, { value: serialized, expiresAt });
  }

  async del(key: string): Promise<void> {
    const redis = this.redisService.getClient();
    const fullKey = this.buildKey(key);
    if (redis) {
      await redis.del(fullKey);
      return;
    }

    this.memory.delete(fullKey);
  }

  async remember<T>(key: string, ttlSeconds: number, factory: () => Promise<T>): Promise<T> {
    const cached = await this.get<T>(key);
    if (cached !== null) {
      return cached;
    }

    const value = await factory();
    await this.set(key, value, ttlSeconds);
    return value;
  }

  async incr(key: string, ttlSeconds?: number): Promise<number> {
    const redis = this.redisService.getClient();
    const fullKey = this.buildKey(key);

    if (redis) {
      const nextValue = await redis.incr(fullKey);
      if (ttlSeconds && ttlSeconds > 0 && nextValue === 1) {
        await redis.expire(fullKey, ttlSeconds);
      }
      return nextValue;
    }

    const existing = await this.get<number>(key);
    const nextValue = (existing ?? 0) + 1;
    await this.set(key, nextValue, ttlSeconds);
    return nextValue;
  }

  private buildKey(key: string): string {
    return `${this.redisService.getKeyPrefix()}:${key}`;
  }
}
