/**
 * Redis Cache Adapter
 *
 * Implements CachePort interface using Redis (ioredis)
 */

import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';
import { CachePort } from '@domain/ports/out/cache.port';

@Injectable()
export class RedisCacheAdapter implements CachePort, OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(RedisCacheAdapter.name);
  private client: Redis;
  private stats = {
    hits: 0,
    misses: 0,
  };

  constructor(private readonly configService: ConfigService) {}

  async onModuleInit(): Promise<void> {
    const host = this.configService.get<string>('REDIS_HOST', 'localhost');
    const port = this.configService.get<number>('REDIS_PORT', 6379);
    const password = this.configService.get<string>('REDIS_PASSWORD');
    const db = this.configService.get<number>('REDIS_DB', 0);

    this.client = new Redis({
      host,
      port,
      password,
      db,
      retryStrategy: times => {
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
      maxRetriesPerRequest: 3,
    });

    this.client.on('connect', () => {
      this.logger.log(`Connected to Redis at ${host}:${port}`);
    });

    this.client.on('error', err => {
      this.logger.error(`Redis connection error: ${err.message}`);
    });

    this.client.on('ready', () => {
      this.logger.log('Redis client ready');
    });
  }

  async onModuleDestroy(): Promise<void> {
    await this.client.quit();
    this.logger.log('Redis connection closed');
  }

  async get<T>(key: string): Promise<T | null> {
    try {
      const value = await this.client.get(key);

      if (value === null) {
        this.stats.misses++;
        return null;
      }

      this.stats.hits++;
      return JSON.parse(value) as T;
    } catch (error: any) {
      this.logger.error(`Error getting key ${key}: ${error?.message || 'Unknown error'}`);
      return null;
    }
  }

  async set<T>(key: string, value: T, ttlSeconds?: number): Promise<void> {
    try {
      const serialized = JSON.stringify(value);
      if (ttlSeconds) {
        await this.client.setex(key, ttlSeconds, serialized);
      } else {
        await this.client.set(key, serialized);
      }
    } catch (error: any) {
      this.logger.error(`Error setting key ${key}: ${error?.message || 'Unknown error'}`);
      throw error;
    }
  }

  async delete(key: string): Promise<void> {
    try {
      await this.client.del(key);
    } catch (error: any) {
      this.logger.error(`Error deleting key ${key}: ${error?.message || 'Unknown error'}`);
      throw error;
    }
  }

  async deleteMany(keys: string[]): Promise<void> {
    if (keys.length === 0) return;

    try {
      await this.client.del(...keys);
    } catch (error: any) {
      this.logger.error(`Error deleting keys: ${error?.message || 'Unknown error'}`);
      throw error;
    }
  }

  async exists(key: string): Promise<boolean> {
    try {
      const result = await this.client.exists(key);
      return result === 1;
    } catch (error: any) {
      this.logger.error(
        `Error checking key existence ${key}: ${error?.message || 'Unknown error'}`
      );
      return false;
    }
  }

  async ttl(key: string): Promise<number> {
    try {
      return await this.client.ttl(key);
    } catch (error: any) {
      this.logger.error(`Error getting TTL for key ${key}: ${error?.message || 'Unknown error'}`);
      return -2;
    }
  }

  async clear(): Promise<void> {
    try {
      await this.client.flushdb();
      this.logger.warn('Redis database cleared');
    } catch (error: any) {
      this.logger.error(`Error clearing cache: ${error?.message || 'Unknown error'}`);
      throw error;
    }
  }

  async getStats(): Promise<{
    hits: number;
    misses: number;
    hitRate: number;
    keyCount: number;
  }> {
    try {
      const keyCount = await this.client.dbsize();
      const total = this.stats.hits + this.stats.misses;
      const hitRate = total > 0 ? this.stats.hits / total : 0;

      return {
        hits: this.stats.hits,
        misses: this.stats.misses,
        hitRate: Math.round(hitRate * 10000) / 100, // Percentage with 2 decimals
        keyCount,
      };
    } catch (error: any) {
      this.logger.error(`Error getting stats: ${error?.message || 'Unknown error'}`);
      return {
        hits: this.stats.hits,
        misses: this.stats.misses,
        hitRate: 0,
        keyCount: 0,
      };
    }
  }

  /**
   * Reset statistics (useful for testing)
   */
  resetStats(): void {
    this.stats.hits = 0;
    this.stats.misses = 0;
  }

  /**
   * Get Redis client (for advanced usage)
   */
  getClient(): Redis {
    return this.client;
  }
}
