/**
 * Redis-based Persistent Credential Storage
 * API keys are encrypted and stored in Redis for persistence across restarts
 */

import Redis from 'ioredis';
import crypto from 'crypto';

const CREDENTIALS_KEY_PREFIX = 'ai:provider:creds:';
const DEFAULT_DEV_KEY = 'default-dev-key-change-in-production';

function resolveEncryptionKey(): string {
  const k = process.env['ENCRYPTION_KEY'];
  if (process.env['NODE_ENV'] === 'production') {
    if (!k || k.length < 16 || k === DEFAULT_DEV_KEY) {
      throw new Error(
        'ENCRYPTION_KEY must be set to a strong value (not the default) in production for Redis credential storage'
      );
    }
    return k;
  }
  return k && k.length >= 8 ? k : DEFAULT_DEV_KEY;
}

export class RedisCredentialsStore {
  private redis: Redis;

  constructor(redisUrl?: string) {
    const url = redisUrl || process.env['REDIS_URL'] || 'redis://localhost:6379';
    this.redis = new Redis(url, {
      maxRetriesPerRequest: null,
      retryStrategy(times) {
        // Stop retrying after 3 attempts (prevents infinite log spam)
        if (times > 3) {
          console.error('[RedisCredentialsStore] Max retries reached, giving up on Redis connection');
          return null; // Stop retrying
        }
        const delay = Math.min(times * 50, 2000);
        return delay;
      },
      // Fail fast on initial connection timeout
      connectTimeout: 3000,
      lazyConnect: true, // Don't connect immediately, wait for first command
    });

    this.redis.on('error', (err) => {
      // Only log first error, suppress spam
      if (!this.redis.status || this.redis.status === 'connecting') {
        console.error('[RedisCredentialsStore] Redis connection error:', err);
      }
    });

    this.redis.on('connect', () => {
      console.log('[RedisCredentialsStore] Connected to Redis');
    });
  }

  private encrypt(text: string): string {
    const keyMaterial = resolveEncryptionKey();
    const iv = crypto.randomBytes(16);
    const key = crypto.createHash('sha256').update(keyMaterial).digest();
    const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);

    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');

    return iv.toString('hex') + ':' + encrypted;
  }

  private decrypt(encryptedText: string): string {
    const keyMaterial = resolveEncryptionKey();
    const parts = encryptedText.split(':');

    if (parts.length !== 2 || !parts[0] || !parts[1]) {
      throw new Error('Invalid encrypted text format');
    }

    const iv = Buffer.from(parts[0], 'hex');
    const encrypted = parts[1];

    const key = crypto.createHash('sha256').update(keyMaterial).digest();
    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);

    let decrypted: string = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    return decrypted;
  }

  async setCredential(provider: string, apiKey: string): Promise<void> {
    try {
      const encrypted = this.encrypt(apiKey);
      const key = `${CREDENTIALS_KEY_PREFIX}${provider}`;
      await this.redis.set(key, encrypted);

      const stored = await this.redis.get(key);
      if (!stored) {
        throw new Error('Failed to verify credential storage in Redis');
      }

      process.env[`${provider.toUpperCase()}_API_KEY`] = apiKey;

      console.log(`[RedisCredentialsStore] Credential stored and verified for: ${provider}`);
    } catch (error) {
      console.error(`[RedisCredentialsStore] Error storing credential for ${provider}:`, error);
      throw error;
    }
  }

  async getCredential(provider: string): Promise<string | null> {
    try {
      const key = `${CREDENTIALS_KEY_PREFIX}${provider}`;
      const encrypted = await this.redis.get(key);

      if (!encrypted) {
        return null;
      }

      const decrypted = this.decrypt(encrypted);

      process.env[`${provider.toUpperCase()}_API_KEY`] = decrypted;

      return decrypted;
    } catch (error) {
      console.error(`[RedisCredentialsStore] Error retrieving credential for ${provider}:`, error);
      throw error;
    }
  }

  async hasCredential(provider: string): Promise<boolean> {
    try {
      const key = `${CREDENTIALS_KEY_PREFIX}${provider}`;
      const exists = await this.redis.exists(key);
      return exists === 1;
    } catch (error) {
      console.error(`[RedisCredentialsStore] Error checking credential for ${provider}:`, error);
      return false;
    }
  }

  async deleteCredential(provider: string): Promise<void> {
    try {
      const key = `${CREDENTIALS_KEY_PREFIX}${provider}`;
      await this.redis.del(key);

      delete process.env[`${provider.toUpperCase()}_API_KEY`];

      console.log(`[RedisCredentialsStore] Credential deleted for: ${provider}`);
    } catch (error) {
      console.error(`[RedisCredentialsStore] Error deleting credential for ${provider}:`, error);
      throw error;
    }
  }

  /**
   * List all configured providers (non-blocking SCAN instead of KEYS)
   */
  async listProviders(): Promise<string[]> {
    try {
      const pattern = `${CREDENTIALS_KEY_PREFIX}*`;
      const providers: string[] = [];
      let cursor = '0';

      do {
        const [nextCursor, keys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 128);
        cursor = nextCursor;
        for (const redisKey of keys) {
          providers.push(redisKey.replace(CREDENTIALS_KEY_PREFIX, ''));
        }
      } while (cursor !== '0');

      return providers;
    } catch (error) {
      console.error('[RedisCredentialsStore] Error listing providers:', error);
      return [];
    }
  }

  async loadAllToEnv(): Promise<void> {
    try {
      const providers = await this.listProviders();

      if (providers.length === 0) {
        console.log('[RedisCredentialsStore] No credentials found in Redis');
        return;
      }

      console.log(`[RedisCredentialsStore] Found ${providers.length} credential(s) in Redis: ${providers.join(', ')}`);

      let loadedCount = 0;
      for (const provider of providers) {
        try {
          const credential = await this.getCredential(provider);
          if (credential) {
            loadedCount++;
            console.log(`[RedisCredentialsStore] ✓ Loaded credential for: ${provider}`);
          } else {
            console.warn(`[RedisCredentialsStore] ⚠ Credential for ${provider} exists but could not be decrypted`);
          }
        } catch (error) {
          console.error(`[RedisCredentialsStore] ✗ Failed to load credential for ${provider}:`, error);
        }
      }

      console.log(`[RedisCredentialsStore] Successfully loaded ${loadedCount}/${providers.length} credentials to environment`);
    } catch (error) {
      console.error('[RedisCredentialsStore] Error loading credentials:', error);
      throw error;
    }
  }

  async close(): Promise<void> {
    await this.redis.quit();
  }
}

let storeInstance: RedisCredentialsStore | null = null;

export function getCredentialsStore(): RedisCredentialsStore {
  if (!storeInstance) {
    storeInstance = new RedisCredentialsStore();
  }
  return storeInstance;
}
