/**
 * Session Cache Service
 *
 * Redis-based caching for MPC session data.
 */

import { Injectable, Logger, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Redis from 'ioredis';

export interface CachedSessionInfo {
  sessionId: string;
  sessionType: string;
  participants: string[];
  thresholdN: number;
  thresholdT: number;
  status: string;
  createdAt: string;
}

@Injectable()
export class SessionCacheService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(SessionCacheService.name);
  private redis: Redis;
  private readonly keyPrefix = 'mpc:session:';
  private readonly defaultTTL = 3600; // 1 hour

  constructor(private readonly configService: ConfigService) {}

  async onModuleInit() {
    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') || 5;

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

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

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

  async onModuleDestroy() {
    await this.redis.quit();
    this.logger.log('Redis disconnected');
  }

  /**
   * Cache session info
   */
  async cacheSession(sessionId: string, info: CachedSessionInfo, ttl?: number): Promise<void> {
    const key = this.keyPrefix + sessionId;
    await this.redis.setex(key, ttl || this.defaultTTL, JSON.stringify(info));
    this.logger.debug(`Cached session: ${sessionId}`);
  }

  /**
   * Get cached session info
   */
  async getSession(sessionId: string): Promise<CachedSessionInfo | null> {
    const key = this.keyPrefix + sessionId;
    const data = await this.redis.get(key);

    if (!data) {
      return null;
    }

    return JSON.parse(data);
  }

  /**
   * Update session status in cache
   */
  async updateSessionStatus(sessionId: string, status: string): Promise<void> {
    const session = await this.getSession(sessionId);

    if (session) {
      session.status = status;
      await this.cacheSession(sessionId, session);
    }
  }

  /**
   * Remove session from cache
   */
  async removeSession(sessionId: string): Promise<void> {
    const key = this.keyPrefix + sessionId;
    await this.redis.del(key);
    this.logger.debug(`Removed session from cache: ${sessionId}`);
  }

  /**
   * Check if session exists in cache
   */
  async hasSession(sessionId: string): Promise<boolean> {
    const key = this.keyPrefix + sessionId;
    return (await this.redis.exists(key)) === 1;
  }

  /**
   * Cache session message for relay
   */
  async cacheMessage(sessionId: string, messageId: string, message: any, ttl?: number): Promise<void> {
    const key = `${this.keyPrefix}${sessionId}:msg:${messageId}`;
    await this.redis.setex(key, ttl || 300, JSON.stringify(message));
  }

  /**
   * Get pending messages for a session
   */
  async getPendingMessages(sessionId: string, partyId: string): Promise<any[]> {
    const pattern = `${this.keyPrefix}${sessionId}:msg:*`;
    const keys = await this.redis.keys(pattern);
    const messages: any[] = [];

    for (const key of keys) {
      const data = await this.redis.get(key);
      if (data) {
        const message = JSON.parse(data);
        // Filter messages for this party
        if (!message.toParties || message.toParties.includes(partyId)) {
          messages.push(message);
        }
      }
    }

    return messages;
  }

  /**
   * Generic key-value operations
   */
  async set(key: string, value: any, ttl?: number): Promise<void> {
    const fullKey = this.keyPrefix + key;
    if (ttl) {
      await this.redis.setex(fullKey, ttl, JSON.stringify(value));
    } else {
      await this.redis.set(fullKey, JSON.stringify(value));
    }
  }

  async get<T>(key: string): Promise<T | null> {
    const fullKey = this.keyPrefix + key;
    const data = await this.redis.get(fullKey);
    return data ? JSON.parse(data) : null;
  }

  async delete(key: string): Promise<void> {
    const fullKey = this.keyPrefix + key;
    await this.redis.del(fullKey);
  }
}
