/**
 * ModelsService - Manages available models from the backend API
 *
 * Purpose:
 * - Fetch available models from /api/llm/models endpoint
 * - Cache models for the session
 * - Provide model names for routing
 * - Show errors when API is unavailable
 */

class ModelsService {
  constructor(config, logger) {
    this.config = config;
    this.logger = logger;

    this.models = null;
    this.lastFetched = null;
    this.isLoading = false;
    this.cacheExpiry = 5 * 60 * 1000; // 5 minutes cache

    // Use config backend URL (set via LOXIA_BACKEND_URL env var), fallback to local server
    this.backendUrl = config.backend?.baseUrl || config.backendUrl || 'http://localhost:8080';

    // Retry configuration
    this.retryAttempts = 0;
    this.maxRetries = 3;
    this.retryDelay = 2000; // Start with 2 seconds
    this.retryTimer = null;
    this.lastError = null;

    // API Key Manager reference (will be set by LoxiaSystem)
    this.apiKeyManager = null;
  }

  /**
   * Set API key manager instance
   * @param {ApiKeyManager} apiKeyManager - API key manager instance
   */
  setApiKeyManager(apiKeyManager) {
    this.apiKeyManager = apiKeyManager;
    
    this.logger?.info('API key manager set for models service', {
      hasManager: !!apiKeyManager
    });
  }

  /**
   * Initialize the models service and fetch initial data
   */
  async initialize() {
    try {
      await this.fetchModels();
      this.logger.info('Models service initialized', {
        modelCount: this.models?.length || 0
      });
    } catch (error) {
      this.lastError = error.message;
      this.logger.error('Failed to initialize models service - NO FALLBACK AVAILABLE', {
        error: error.message,
        endpoint: this.backendUrl,
        willRetry: true
      });
      // Schedule retry instead of using hardcoded fallback
      this._scheduleRetry();
    }
  }

  /**
   * Get available model names for routing
   */
  getAvailableModelNames() {
    if (!this.models || this.models.length === 0) {
      this.logger.error('No models available - API fetch failed', {
        lastError: this.lastError,
        endpoint: this.backendUrl,
        retryAttempt: this.retryAttempts,
        maxRetries: this.maxRetries
      });
      return [];
    }

    return this.models.map(model => model.name);
  }

  /**
   * Get all model information
   */
  getModels() {
    if (!this.models || this.models.length === 0) {
      this.logger.error('No models available - API fetch failed', {
        lastError: this.lastError,
        endpoint: this.backendUrl
      });
      return [];
    }
    return this.models;
  }

  /**
   * Fetch models from backend API
   * @param {Object} context - Optional context with API key
   */
  async fetchModels(context = null) {
    if (this.isLoading) {
      this.logger.debug('Models fetch already in progress');
      return;
    }

    this.isLoading = true;
    // Local server uses /api prefix, Azure backend doesn't
    const isLocalServer = this.backendUrl.includes('localhost') || this.backendUrl.includes('127.0.0.1');
    const url = isLocalServer ? `${this.backendUrl}/api/llm/models` : `${this.backendUrl}/llm/models`;

    try {
      
      // Get API key from multiple sources
      let apiKey = null;

      // First try to get from API key manager (global storage)
      if (this.apiKeyManager) {
        const keys = this.apiKeyManager.getKeysForRequest(null, {
          platformProvided: true, // Models endpoint uses platform key
          vendor: null
        });
        apiKey = keys.loxiaApiKey;
      }

      // Fallback to context, config, then environment
      if (!apiKey && context && context.apiKey) {
        apiKey = context.apiKey;
      } else if (!apiKey && this.config.apiKey) {
        apiKey = this.config.apiKey;
      } else if (!apiKey && process.env.LOXIA_API_KEY) {
        apiKey = process.env.LOXIA_API_KEY;
      }

      // Use AbortController for timeout (Node.js fetch doesn't support timeout option)
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 10000);

      const fetchOptions = {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          ...(apiKey && { 'Authorization': `Bearer ${apiKey}` })
        },
        signal: controller.signal
      };

      this.logger.debug('Fetching models from backend', {
        url,
        hasApiKey: !!apiKey,
        apiKeySource: context?.apiKey ? 'context' : this.config.apiKey ? 'config' : process.env.LOXIA_API_KEY ? 'env' : 'none'
      });

      let response;
      try {
        response = await fetch(url, fetchOptions);
        clearTimeout(timeoutId);
      } catch (fetchError) {
        clearTimeout(timeoutId);
        if (fetchError.name === 'AbortError') {
          throw new Error('Request timeout after 10 seconds');
        }
        throw fetchError;
      }

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      const data = await response.json();
      
      if (data.models && Array.isArray(data.models)) {
        this.models = data.models;
        this.lastFetched = new Date();
        this.needsRefresh = false;
        
        this.logger.info('Models fetched from backend', { 
          modelCount: this.models.length,
          models: this.models.map(m => m.name)
        });
      } else {
        throw new Error('Invalid response format from models API');
      }

    } catch (error) {
      this.lastError = error.message;
      this.logger.error('Failed to fetch models from backend - NO FALLBACK', {
        error: error.message,
        endpoint: url,
        retryAttempt: this.retryAttempts,
        maxRetries: this.maxRetries
      });

      // Schedule retry instead of using fallback
      this._scheduleRetry(context);

      // Rethrow if this was initial fetch and we have no models yet
      if (!this.models) {
        throw error;
      }

    } finally {
      this.isLoading = false;
    }
  }

  /**
   * Check if models cache needs refresh
   */
  needsRefresh() {
    if (!this.lastFetched) return true;
    
    const timeSinceUpdate = Date.now() - this.lastFetched.getTime();
    return timeSinceUpdate > this.cacheExpiry;
  }

  /**
   * Force refresh models from backend
   * @param {Object} context - Optional context with API key
   */
  async refresh(context = null) {
    this.logger.info('Force refreshing models');
    await this.fetchModels(context);
  }

  /**
   * Refresh models with API key context if needed
   * @param {Object} context - Context with API key
   */
  async refreshWithContext(context) {
    // Only refresh if we haven't successfully fetched or if we need a refresh
    if (this.needsRefresh || !this.lastFetched) {
      this.logger.debug('Refreshing models with API key context');
      await this.fetchModels(context);
    }
  }

  /**
   * Schedule retry for failed model fetch
   * Implements exponential backoff
   * @private
   */
  _scheduleRetry(context = null) {
    // Clear any existing retry timer
    if (this.retryTimer) {
      clearTimeout(this.retryTimer);
      this.retryTimer = null;
    }

    // Check if we've exceeded max retries
    if (this.retryAttempts >= this.maxRetries) {
      this.logger.error('Max retry attempts reached for model fetch', {
        attempts: this.retryAttempts,
        maxRetries: this.maxRetries,
        lastError: this.lastError
      });
      return;
    }

    // Calculate delay with exponential backoff: 2s, 4s, 8s, etc.
    const delay = this.retryDelay * Math.pow(2, this.retryAttempts);
    this.retryAttempts++;

    this.logger.info('Scheduling model fetch retry', {
      attempt: this.retryAttempts,
      maxRetries: this.maxRetries,
      delayMs: delay
    });

    // Schedule retry
    this.retryTimer = setTimeout(async () => {
      this.logger.info('Executing scheduled model fetch retry', {
        attempt: this.retryAttempts
      });

      try {
        await this.fetchModels(context);
        // Success - reset retry counter
        this.retryAttempts = 0;
        this.lastError = null;
      } catch (error) {
        // fetchModels will handle scheduling next retry
        this.logger.warn('Retry attempt failed', {
          attempt: this.retryAttempts,
          error: error.message
        });
      }
    }, delay);
  }

  /**
   * Cancel any pending retry
   * @private
   */
  _cancelRetry() {
    if (this.retryTimer) {
      clearTimeout(this.retryTimer);
      this.retryTimer = null;
      this.logger.debug('Cancelled pending model fetch retry');
    }
  }

  /**
   * Get service status
   */
  getStatus() {
    return {
      initialized: !!this.models,
      lastFetched: this.lastFetched?.toISOString() || null,
      modelCount: this.models?.length || 0,
      isLoading: this.isLoading,
      needsRefresh: this.needsRefresh(),
      error: this.lastError || null,
      retryAttempts: this.retryAttempts,
      maxRetries: this.maxRetries,
      hasError: !!this.lastError
    };
  }
}

export default ModelsService;