import { createClient } from 'redis';
import { config } from '../config/env';
import logger from '../utils/logger';

export interface SessionData {
  userId: string;
  email: string;
  role: string;
  refreshToken: string;
  lastActivity: number;
}

class SessionService {
  private client: any;
  private isConnected: boolean = false;

  constructor() {
    this.client = createClient({
      url: config.redis.url,
      socket: {
        host: config.redis.host,
        port: config.redis.port,
        reconnectStrategy: (retries) => {
          if (retries > 10) {
            logger.error('Redis connection failed after 10 retries');
            return new Error('Redis connection failed');
          }
          return Math.min(retries * 100, 3000);
        }
      }
    });

    this.setupEventHandlers();
  }

  private setupEventHandlers(): void {
    this.client.on('connect', () => {
      logger.info('Connected to Redis');
      this.isConnected = true;
    });

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

    this.client.on('error', (error: Error) => {
      logger.error('Redis client error:', error);
      this.isConnected = false;
    });

    this.client.on('end', () => {
      logger.info('Redis connection ended');
      this.isConnected = false;
    });

    this.client.on('reconnecting', () => {
      logger.info('Reconnecting to Redis...');
    });
  }

  /**
   * Connect to Redis
   */
  async connect(): Promise<void> {
    if (this.isConnected) {
      return;
    }

    try {
      // Check if client is already connecting or connected
      if (this.client.isOpen) {
        this.isConnected = true;
        return;
      }

      await this.client.connect();
      this.isConnected = true;
      logger.info('Successfully connected to Redis');
    } catch (error) {
      // If it's a "Socket already opened" error, mark as connected
      if (error instanceof Error && error.message.includes('Socket already opened')) {
        this.isConnected = true;
        logger.info('Redis connection already established');
        return;
      }
      
      logger.error('Failed to connect to Redis:', error);
      throw error;
    }
  }

  /**
   * Disconnect from Redis
   */
  async disconnect(): Promise<void> {
    if (!this.isConnected) {
      return;
    }

    try {
      await this.client.quit();
      logger.info('Disconnected from Redis');
    } catch (error) {
      logger.error('Error disconnecting from Redis:', error);
    }
  }

  /**
   * Store user session
   */
  async storeSession(userId: string, sessionData: Omit<SessionData, 'lastActivity'>): Promise<void> {
    try {
      await this.connect();
      
      const session: SessionData = {
        ...sessionData,
        lastActivity: Date.now()
      };

      const key = `session:${userId}`;
      const sessionTTL = parseInt(config.jwt.refreshExpiresIn.replace(/[^0-9]/g, '')) * 
        (config.jwt.refreshExpiresIn.includes('h') ? 3600 : 
         config.jwt.refreshExpiresIn.includes('d') ? 86400 : 60);

      await this.client.setEx(key, sessionTTL, JSON.stringify(session));
      logger.info(`Stored session for user: ${userId}`);
    } catch (error) {
      logger.error('Error storing session:', error);
      throw new Error('Failed to store session');
    }
  }

  /**
   * Get user session
   */
  async getSession(userId: string): Promise<SessionData | null> {
    try {
      await this.connect();
      
      const key = `session:${userId}`;
      const sessionData = await this.client.get(key);

      if (!sessionData) {
        return null;
      }

      const session: SessionData = JSON.parse(sessionData);
      
      // Update last activity
      session.lastActivity = Date.now();
      await this.updateSessionActivity(userId, session.lastActivity);

      logger.info(`Retrieved session for user: ${userId}`);
      return session;
    } catch (error) {
      logger.error('Error getting session:', error);
      return null;
    }
  }

  /**
   * Update session activity timestamp
   */
  async updateSessionActivity(userId: string, lastActivity: number): Promise<void> {
    try {
      await this.connect();
      
      const key = `session:${userId}`;
      const sessionData = await this.client.get(key);

      if (sessionData) {
        const session: SessionData = JSON.parse(sessionData);
        session.lastActivity = lastActivity;
        
        const sessionTTL = parseInt(config.jwt.refreshExpiresIn.replace(/[^0-9]/g, '')) * 
          (config.jwt.refreshExpiresIn.includes('h') ? 3600 : 
           config.jwt.refreshExpiresIn.includes('d') ? 86400 : 60);

        await this.client.setEx(key, sessionTTL, JSON.stringify(session));
      }
    } catch (error) {
      logger.error('Error updating session activity:', error);
    }
  }

  /**
   * Remove user session
   */
  async removeSession(userId: string): Promise<void> {
    try {
      await this.connect();
      
      const key = `session:${userId}`;
      await this.client.del(key);
      
      logger.info(`Removed session for user: ${userId}`);
    } catch (error) {
      logger.error('Error removing session:', error);
      throw new Error('Failed to remove session');
    }
  }

  /**
   * Check if session exists
   */
  async sessionExists(userId: string): Promise<boolean> {
    try {
      await this.connect();
      
      const key = `session:${userId}`;
      const exists = await this.client.exists(key);
      
      return exists === 1;
    } catch (error) {
      logger.error('Error checking session existence:', error);
      return false;
    }
  }

  /**
   * Store refresh token for blacklisting
   */
  async blacklistToken(token: string, expiresIn: number): Promise<void> {
    try {
      await this.connect();
      
      const key = `blacklist:${token}`;
      await this.client.setEx(key, expiresIn, '1');
      
      logger.info('Token blacklisted successfully');
    } catch (error) {
      logger.error('Error blacklisting token:', error);
      throw new Error('Failed to blacklist token');
    }
  }

  /**
   * Check if token is blacklisted
   */
  async isTokenBlacklisted(token: string): Promise<boolean> {
    try {
      await this.connect();
      
      const key = `blacklist:${token}`;
      const exists = await this.client.exists(key);
      
      return exists === 1;
    } catch (error) {
      logger.error('Error checking token blacklist:', error);
      return false;
    }
  }

  /**
   * Get all active sessions (for admin)
   */
  async getAllSessions(): Promise<{ userId: string; session: SessionData }[]> {
    try {
      await this.connect();
      
      const keys = await this.client.keys('session:*');
      const sessions: { userId: string; session: SessionData }[] = [];

      for (const key of keys) {
        const userId = key.replace('session:', '');
        const sessionData = await this.client.get(key);
        
        if (sessionData) {
          sessions.push({
            userId,
            session: JSON.parse(sessionData)
          });
        }
      }

      return sessions;
    } catch (error) {
      logger.error('Error getting all sessions:', error);
      return [];
    }
  }

  /**
   * Clean up expired sessions
   */
  async cleanupExpiredSessions(): Promise<number> {
    try {
      await this.connect();
      
      const keys = await this.client.keys('session:*');
      let cleanedCount = 0;

      for (const key of keys) {
        const sessionData = await this.client.get(key);
        
        if (sessionData) {
          const session: SessionData = JSON.parse(sessionData);
          const now = Date.now();
          const sessionTTL = parseInt(config.jwt.refreshExpiresIn.replace(/[^0-9]/g, '')) * 
            (config.jwt.refreshExpiresIn.includes('h') ? 3600 : 
             config.jwt.refreshExpiresIn.includes('d') ? 86400 : 60) * 1000;

          if (now - session.lastActivity > sessionTTL) {
            await this.client.del(key);
            cleanedCount++;
          }
        }
      }

      logger.info(`Cleaned up ${cleanedCount} expired sessions`);
      return cleanedCount;
    } catch (error) {
      logger.error('Error cleaning up expired sessions:', error);
      return 0;
    }
  }

  /**
   * Get Redis connection status
   */
  getConnectionStatus(): boolean {
    return this.isConnected;
  }
}

// Export singleton instance
export const sessionService = new SessionService(); 