/**
 * @ai-summary Redis client and caching service
 * @ai-context Used by all features for caching external API responses
 */
import Redis from 'ioredis';
import { logger } from '../logging/logger';
import { appConfig } from './config-loader';

export const redis = new Redis(appConfig.getRedisUrl(), {
  retryStrategy: (times) => Math.min(times * 50, 2000),
});

redis.on('connect', () => {
  logger.info('Redis connected');
});

redis.on('error', (err) => {
  logger.error('Redis error', { error: err.message });
});

export class CacheService {
  private prefix = 'mvp:';
  
  async get<T>(key: string): Promise<T | null> {
    try {
      const data = await redis.get(this.prefix + key);
      return data ? JSON.parse(data) : null;
    } catch (error) {
      logger.error('Cache get error', { key, error });
      return null;
    }
  }
  
  async set(key: string, value: any, ttl?: number): Promise<void> {
    try {
      const data = JSON.stringify(value);
      if (ttl) {
        await redis.setex(this.prefix + key, ttl, data);
      } else {
        await redis.set(this.prefix + key, data);
      }
    } catch (error) {
      logger.error('Cache set error', { key, error });
    }
  }
  
  async del(key: string): Promise<void> {
    try {
      await redis.del(this.prefix + key);
    } catch (error) {
      logger.error('Cache delete error', { key, error });
    }
  }

  /**
   * Delete all keys matching the provided pattern (without namespace prefix).
   * Uses SCAN to avoid blocking Redis for large keyspaces.
   */
  async deletePattern(pattern: string): Promise<void> {
    try {
      const namespacedPattern = this.prefix + pattern;
      let cursor = '0';

      do {
        const [nextCursor, keys] = await redis.scan(
          cursor,
          'MATCH',
          namespacedPattern,
          'COUNT',
          100
        );
        cursor = nextCursor;

        if (keys.length > 0) {
          await redis.del(...keys);
        }
      } while (cursor !== '0');
    } catch (error) {
      logger.error('Cache delete pattern error', { pattern, error });
    }
  }
}

export const cacheService = new CacheService();

/**
 * Distributed lock service for preventing concurrent operations across containers
 */
export class DistributedLockService {
  private prefix = 'mvp:lock:';

  /**
   * Attempts to acquire a lock with the given key
   * @param key Lock identifier
   * @param ttlSeconds Time-to-live in seconds (auto-release)
   * @param lockValue Unique identifier for this lock holder
   * @returns true if lock acquired, false if already held
   */
  async acquireLock(key: string, ttlSeconds: number, lockValue: string): Promise<boolean> {
    try {
      // SET NX (only if not exists) with EX (expiry)
      const result = await redis.set(
        this.prefix + key,
        lockValue,
        'EX',
        ttlSeconds,
        'NX'
      );
      return result === 'OK';
    } catch (error) {
      logger.error('Lock acquisition error', { key, error });
      return false;
    }
  }

  /**
   * Releases a lock only if we hold it (compare lockValue)
   * @param key Lock identifier
   * @param lockValue The value used when acquiring the lock
   * @returns true if lock was released, false if we didn't hold it
   */
  async releaseLock(key: string, lockValue: string): Promise<boolean> {
    try {
      // Lua script to atomically check and delete
      const script = `
        if redis.call("get", KEYS[1]) == ARGV[1] then
          return redis.call("del", KEYS[1])
        else
          return 0
        end
      `;
      const result = await redis.eval(script, 1, this.prefix + key, lockValue);
      return result === 1;
    } catch (error) {
      logger.error('Lock release error', { key, error });
      return false;
    }
  }

  /**
   * Checks if a lock is currently held
   * @param key Lock identifier
   * @returns true if lock exists
   */
  async isLocked(key: string): Promise<boolean> {
    try {
      const exists = await redis.exists(this.prefix + key);
      return exists === 1;
    } catch (error) {
      logger.error('Lock check error', { key, error });
      return false;
    }
  }
}

export const lockService = new DistributedLockService();
