/**
 * QM Session Manager - Centralized session state management
 */

import type { CacheProvider } from '@stock-bot/cache';
import type { Logger } from '@stock-bot/types';
import { QM_SESSION_IDS, SESSION_CONFIG } from './config';
import type { CachedSession, QMSession } from './types';

export class QMSessionManager {
  private static instance: QMSessionManager | null = null;
  private cacheProvider?: CacheProvider;
  private logger?: Logger;

  private constructor() {}

  static getInstance(): QMSessionManager {
    if (!this.instance) {
      this.instance = new QMSessionManager();
    }
    return this.instance;
  }

  /**
   * Reset the singleton instance (mainly for testing)
   */
  static resetInstance(): void {
    if (this.instance) {
      this.instance = null;
    }
  }

  /**
   * Set the cache provider
   */
  setCacheProvider(cache: CacheProvider): void {
    this.cacheProvider = cache;
  }

  /**
   * Set the logger
   */
  setLogger(logger: Logger): void {
    this.logger = logger;
  }

  /**
   * Initialize the session manager with cache and logger
   */
  async initialize(cache?: CacheProvider, logger?: Logger): Promise<void> {
    if (cache) {
      this.setCacheProvider(cache);
    }
    if (logger) {
      this.setLogger(logger);
    }
  }

  /**
   * Get an active session for the given session ID
   * Implements round-robin selection with health checks
   */
  async getSession(sessionId: string): Promise<QMSession | null> {
    if (!this.cacheProvider) {
      this.logger?.error('No cache provider available');
      return null;
    }

    try {
      // Get all sessions for this session type
      const sessions = await this.getSessions(sessionId);
      
      // Filter out failed and stale sessions
      const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000);
      const validSessions = sessions.filter(
        session => session.failedCalls <= SESSION_CONFIG.MAX_FAILED_CALLS &&
                   session.lastUsed >= tenMinutesAgo
      );

      this.logger?.trace(`Found ${validSessions.length} valid sessions for ${sessionId}`, {
        sessionId,
        totalSessions: sessions.length,
        validSessions: validSessions.length,
      });

      if (validSessions.length === 0) {
        this.logger?.warn(`No valid sessions available for ${sessionId}`, {
          sessionId,
          totalSessions: sessions.length,
          maxFailedCalls: SESSION_CONFIG.MAX_FAILED_CALLS,
        });
        return null;
      }

      // Sort by least recently used
      validSessions.sort((a, b) => 
        new Date(a.lastUsed).getTime() - new Date(b.lastUsed).getTime()
      );

      // Return the least recently used session
      return validSessions[0] || null;
    } catch (error) {
      this.logger?.error('Failed to get session', { error, sessionId });
      return null;
    }
  }

  /**
   * Get a session by its UUID
   */
  async getSessionByUuid(sessionId: string, uuid: string): Promise<QMSession | null> {
    const sessions = await this.getSessions(sessionId);
    return sessions.find(s => s.uuid === uuid) || null;
  }

  /**
   * Add a new session
   */
  async addSession(sessionId: string, session: QMSession): Promise<void> {
    if (!this.cacheProvider) {
      this.logger?.error('No cache provider available');
      return;
    }

    try {
      // Get session type from session ID
      const sessionType = Object.entries(QM_SESSION_IDS).find(([_, id]) => id === sessionId)?.[0] || 'UNKNOWN';
      
      // Store the session with a unique key
      const sessionKey = `qm:sessions:${sessionType.toLowerCase()}:${session.uuid}`;
      const cachedSession: CachedSession = {
        ...session,
        lastUsed: session.lastUsed instanceof Date ? session.lastUsed.toISOString() : session.lastUsed,
        createdAt: session.createdAt instanceof Date ? session.createdAt.toISOString() : session.createdAt,
        id: session.uuid,
        sessionType,
      };

      await this.cacheProvider.set(sessionKey, cachedSession, 86400); // 24 hour TTL

      // Update the session list
      const listKey = `qm:sessions:${sessionType.toLowerCase()}:list`;
      const sessionIds = await this.cacheProvider.get<string[]>(listKey) || [];
      if (!sessionIds.includes(session.uuid)) {
        sessionIds.push(session.uuid);
        await this.cacheProvider.set(listKey, sessionIds, 86400);
      }

      this.logger?.info(`Added new session`, { sessionId, uuid: session.uuid });
    } catch (error) {
      this.logger?.error('Failed to add session', { error, sessionId });
    }
  }

  /**
   * Get all sessions for a session ID
   */
  async getSessions(sessionId: string): Promise<QMSession[]> {
    if (!this.cacheProvider) {
      return [];
    }

    try {
      const sessionType = Object.entries(QM_SESSION_IDS).find(([_, id]) => id === sessionId)?.[0] || 'UNKNOWN';
      const listKey = `qm:sessions:${sessionType.toLowerCase()}:list`;
      const sessionIds = await this.cacheProvider.get<string[]>(listKey) || [];

      const sessions: QMSession[] = [];
      for (const uuid of sessionIds) {
        const sessionKey = `qm:sessions:${sessionType.toLowerCase()}:${uuid}`;
        const cachedSession = await this.cacheProvider.get<CachedSession>(sessionKey);
        
        if (cachedSession) {
          sessions.push({
            uuid: cachedSession.uuid,
            proxy: cachedSession.proxy,
            headers: cachedSession.headers,
            successfulCalls: cachedSession.successfulCalls || 0,
            failedCalls: cachedSession.failedCalls || 0,
            lastUsed: new Date(cachedSession.lastUsed),
            createdAt: cachedSession.createdAt ? new Date(cachedSession.createdAt) : new Date(),
          });
        }
      }

      return sessions;
    } catch (error) {
      this.logger?.error('Failed to get sessions', { error, sessionId });
      return [];
    }
  }

  /**
   * Get session count for a session ID
   */
  async getSessionCount(sessionId: string): Promise<number> {
    const sessions = await this.getSessions(sessionId);
    const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000);
    return sessions.filter(s => 
      (s.failedCalls || 0) <= SESSION_CONFIG.MAX_FAILED_CALLS &&
      s.lastUsed >= tenMinutesAgo
    ).length;
  }

  /**
   * Cleanup failed and stale sessions
   */
  async cleanupFailedSessions(): Promise<number> {
    if (!this.cacheProvider) {
      return 0;
    }

    let removedCount = 0;

    try {
      for (const [sessionType, sessionId] of Object.entries(QM_SESSION_IDS)) {
        const sessions = await this.getSessions(sessionId);
        const listKey = `qm:sessions:${sessionType.toLowerCase()}:list`;
        const updatedList: string[] = [];

        for (const session of sessions) {
          const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000);
          const isStale = session.lastUsed < tenMinutesAgo;
          const isFailed = (session.failedCalls || 0) > SESSION_CONFIG.MAX_FAILED_CALLS;
          
          if (isFailed || isStale) {
            // Delete the failed or stale session
            const sessionKey = `qm:sessions:${sessionType.toLowerCase()}:${session.uuid}`;
            await this.cacheProvider.del(sessionKey);
            removedCount++;
            this.logger?.info(`Removed ${isFailed ? 'failed' : 'stale'} session`, { 
              sessionType, 
              uuid: session.uuid, 
              failedCalls: session.failedCalls,
              lastUsed: session.lastUsed,
              isStale,
              isFailed
            });
          } else {
            updatedList.push(session.uuid);
          }
        }

        // Update the session list
        if (updatedList.length > 0) {
          await this.cacheProvider.set(listKey, updatedList, 86400);
        } else {
          await this.cacheProvider.del(listKey);
        }
      }
    } catch (error) {
      this.logger?.error('Failed to cleanup sessions', { error });
    }

    return removedCount;
  }

  /**
   * Check if we need more sessions for a session ID
   */
  async needsMoreSessions(sessionId: string): Promise<boolean> {
    const validCount = await this.getSessionCount(sessionId);
    return validCount < SESSION_CONFIG.MAX_SESSIONS;
  }

  /**
   * Check if we're at capacity for a session ID
   */
  async isAtCapacity(sessionId: string): Promise<boolean> {
    const sessions = await this.getSessions(sessionId);
    return sessions.length >= SESSION_CONFIG.MAX_SESSIONS;
  }

  /**
   * Get statistics about sessions
   */
  async getStats(): Promise<Record<string, {
    total: number;
    valid: number;
    failed: number;
    successfulCalls: number;
    failedCalls: number;
  }>> {
    const stats: Record<string, {
      total: number;
      valid: number;
      failed: number;
      successfulCalls: number;
      failedCalls: number;
    }> = {};
    
    for (const [sessionType, sessionId] of Object.entries(QM_SESSION_IDS)) {
      const sessions = await this.getSessions(sessionId);
      const validSessions = sessions.filter(
        session => session.failedCalls <= SESSION_CONFIG.MAX_FAILED_CALLS
      );
      const failedSessions = sessions.filter(
        session => session.failedCalls > SESSION_CONFIG.MAX_FAILED_CALLS
      );

      stats[sessionType] = {
        total: sessions.length,
        valid: validSessions.length,
        failed: failedSessions.length,
        successfulCalls: sessions.reduce((sum, s) => sum + s.successfulCalls, 0),
        failedCalls: sessions.reduce((sum, s) => sum + s.failedCalls, 0),
      };
    }

    return stats;
  }

  /**
   * Increment failed calls for a session
   */
  async incrementFailedCalls(sessionId: string, sessionUuid: string): Promise<void> {
    if (!this.cacheProvider) {
      return;
    }

    try {
      const sessionType = Object.entries(QM_SESSION_IDS).find(([_, id]) => id === sessionId)?.[0] || 'UNKNOWN';
      const sessionKey = `qm:sessions:${sessionType.toLowerCase()}:${sessionUuid}`;
      const cachedSession = await this.cacheProvider.get<CachedSession>(sessionKey);
      
      if (cachedSession) {
        cachedSession.failedCalls = (cachedSession.failedCalls || 0) + 1;
        cachedSession.lastUsed = new Date().toISOString();
        
        await this.cacheProvider.set(sessionKey, cachedSession, 86400);
        
        this.logger?.debug(`Incremented failed calls for session`, {
          sessionUuid,
          failedCalls: cachedSession.failedCalls,
          sessionId
        });
      } else {
        this.logger?.warn(`Session not found`, { sessionUuid, sessionId });
      }
    } catch (error) {
      this.logger?.error('Failed to increment failed calls', { error, sessionUuid, sessionId });
    }
  }

  /**
   * Increment successful calls for a session
   */
  async incrementSuccessfulCalls(sessionId: string, sessionUuid: string): Promise<void> {
    if (!this.cacheProvider) {
      return;
    }

    try {
      const sessionType = Object.entries(QM_SESSION_IDS).find(([_, id]) => id === sessionId)?.[0] || 'UNKNOWN';
      const sessionKey = `qm:sessions:${sessionType.toLowerCase()}:${sessionUuid}`;
      const cachedSession = await this.cacheProvider.get<CachedSession>(sessionKey);
      
      if (cachedSession) {
        cachedSession.successfulCalls = (cachedSession.successfulCalls || 0) + 1;
        cachedSession.lastUsed = new Date().toISOString();
        
        await this.cacheProvider.set(sessionKey, cachedSession, 86400);
        
        this.logger?.debug(`Incremented successful calls for session`, {
          sessionUuid,
          successfulCalls: cachedSession.successfulCalls,
          sessionId
        });
      } else {
        this.logger?.warn(`Session not found`, { sessionUuid, sessionId });
      }
    } catch (error) {
      this.logger?.error('Failed to increment successful calls', { error, sessionUuid, sessionId });
    }
  }
}
