/**
 * Centralized Proxy Manager - Handles proxy storage, retrieval, and caching
 */
import type { CacheProvider } from '@stock-bot/cache';
import type { ProxyInfo, ProxyManagerConfig, ProxyStats } from './types';

export class ProxyManager {
  private cache: CacheProvider;
  private proxies: ProxyInfo[] = [];
  private proxyIndex: number = 0;
  private lastUpdate: Date | null = null;
  private lastFetchTime: Date | null = null;
  private isInitialized = false;
  private logger: any;
  private config: ProxyManagerConfig;

  constructor(cache: CacheProvider, config: ProxyManagerConfig = {}, logger?: any) {
    this.cache = cache;
    this.config = config;
    this.logger = logger || console;
  }

  /**
   * Internal initialization - loads existing proxies from cache
   */
  private async initializeInternal(): Promise<void> {
    if (this.isInitialized) {
      return;
    }

    try {
      this.logger.info('Initializing proxy manager...');

      // Wait for cache to be ready
      await this.cache.waitForReady(10000); // Wait up to 10 seconds
      this.logger.debug('Cache is ready');

      await this.loadFromCache();
      this.isInitialized = true;
      this.logger.info('Proxy manager initialized', {
        proxiesLoaded: this.proxies.length,
        lastUpdate: this.lastUpdate,
      });
    } catch (error) {
      this.logger.error('Failed to initialize proxy manager', { error });
      this.isInitialized = true; // Set to true anyway to avoid infinite retries
    }
  }

  getProxy(): string | null {
    if (this.proxies.length === 0) {
      this.logger.warn('No proxies available in memory');
      return null;
    }

    // Cycle through proxies
    if (this.proxyIndex >= this.proxies.length) {
      this.proxyIndex = 0;
    }

    const proxyInfo = this.proxies[this.proxyIndex++];
    if (!proxyInfo) {
      return null;
    }

    // Build proxy URL with optional auth
    let proxyUrl = `${proxyInfo.protocol}://`;
    if (proxyInfo.username && proxyInfo.password) {
      proxyUrl += `${proxyInfo.username}:${proxyInfo.password}@`;
    }
    proxyUrl += `${proxyInfo.host}:${proxyInfo.port}`;

    return proxyUrl;
  }
  /**
   * Get a random working proxy from the available pool (synchronous)
   */
  getRandomProxy(): ProxyInfo | null {
    // Ensure initialized
    if (!this.isInitialized) {
      throw new Error('ProxyManager not initialized');
    }

    // Return null if no proxies available
    if (this.proxies.length === 0) {
      this.logger.warn('No proxies available in memory');
      return null;
    }

    // Filter for working proxies (not explicitly marked as non-working)
    const workingProxies = this.proxies.filter(proxy => proxy.isWorking !== false);

    if (workingProxies.length === 0) {
      this.logger.warn('No working proxies available');
      return null;
    }

    // Return random proxy with preference for recently successful ones
    const sortedProxies = workingProxies.sort((a, b) => {
      // Prefer proxies with better success rates
      const aRate = a.successRate || 0;
      const bRate = b.successRate || 0;
      return bRate - aRate;
    });

    // Take from top 50% of best performing proxies
    const topProxies = sortedProxies.slice(0, Math.max(1, Math.floor(sortedProxies.length * 0.5)));
    const selectedProxy = topProxies[Math.floor(Math.random() * topProxies.length)];

    if (!selectedProxy) {
      this.logger.warn('No proxy selected from available pool');
      return null;
    }

    this.logger.debug('Selected proxy', {
      host: selectedProxy.host,
      port: selectedProxy.port,
      successRate: selectedProxy.successRate,
      totalAvailable: workingProxies.length,
    });

    return selectedProxy;
  }

  /**
   * Get all working proxies (synchronous)
   */
  getWorkingProxies(): ProxyInfo[] {
    if (!this.isInitialized) {
      throw new Error('ProxyManager not initialized');
    }

    return this.proxies.filter(proxy => proxy.isWorking !== false);
  }

  /**
   * Get all proxies (working and non-working)
   */
  getAllProxies(): ProxyInfo[] {
    if (!this.isInitialized) {
      throw new Error('ProxyManager not initialized');
    }

    return [...this.proxies];
  }

  /**
   * Get proxy statistics
   */
  getStats(): ProxyStats {
    if (!this.isInitialized) {
      throw new Error('ProxyManager not initialized');
    }

    return {
      total: this.proxies.length,
      working: this.proxies.filter(p => p.isWorking !== false).length,
      failed: this.proxies.filter(p => p.isWorking === false).length,
      lastUpdate: this.lastUpdate,
    };
  }

  /**
   * Update the proxy pool with new proxies
   */
  async updateProxies(proxies: ProxyInfo[]): Promise<void> {
    // Ensure manager is initialized before updating
    if (!this.isInitialized) {
      await this.initializeInternal();
    }

    try {
      this.logger.info('Updating proxy pool', {
        newCount: proxies.length,
        existingCount: this.proxies.length,
      });

      this.proxies = proxies;
      this.lastUpdate = new Date();

      // Store to cache (keys will be prefixed with cache:proxy: automatically)
      await this.cache.set('active', proxies);
      await this.cache.set('last-update', this.lastUpdate.toISOString());

      const workingCount = proxies.filter(p => p.isWorking !== false).length;
      this.logger.info('Proxy pool updated successfully', {
        totalProxies: proxies.length,
        workingProxies: workingCount,
        lastUpdate: this.lastUpdate,
      });
    } catch (error) {
      this.logger.error('Failed to update proxy pool', { error });
      throw error;
    }
  }

  /**
   * Add or update a single proxy in the pool
   */
  async updateProxy(proxy: ProxyInfo): Promise<void> {
    const existingIndex = this.proxies.findIndex(
      p => p.host === proxy.host && p.port === proxy.port && p.protocol === proxy.protocol
    );

    if (existingIndex >= 0) {
      this.proxies[existingIndex] = { ...this.proxies[existingIndex], ...proxy };
      this.logger.debug('Updated existing proxy', { host: proxy.host, port: proxy.port });
    } else {
      this.proxies.push(proxy);
      this.logger.debug('Added new proxy', { host: proxy.host, port: proxy.port });
    }

    // Update cache
    await this.updateProxies(this.proxies);
  }

  /**
   * Remove a proxy from the pool
   */
  async removeProxy(host: string, port: number, protocol: string): Promise<void> {
    const initialLength = this.proxies.length;
    this.proxies = this.proxies.filter(
      p => !(p.host === host && p.port === port && p.protocol === protocol)
    );

    if (this.proxies.length < initialLength) {
      await this.updateProxies(this.proxies);
      this.logger.debug('Removed proxy', { host, port, protocol });
    }
  }

  /**
   * Clear all proxies from memory and cache
   */
  async clearProxies(): Promise<void> {
    this.proxies = [];
    this.lastUpdate = null;

    await this.cache.del('active');
    await this.cache.del('last-update');

    this.logger.info('Cleared all proxies');
  }

  /**
   * Check if proxy manager is ready
   */
  isReady(): boolean {
    return this.isInitialized;
  }

  /**
   * Load proxies from cache storage
   */
  private async loadFromCache(): Promise<void> {
    try {
      const cachedProxies = await this.cache.get<ProxyInfo[]>('active');
      const lastUpdateStr = await this.cache.get<string>('last-update');

      if (cachedProxies && Array.isArray(cachedProxies)) {
        this.proxies = cachedProxies;
        this.lastUpdate = lastUpdateStr ? new Date(lastUpdateStr) : null;

        this.logger.debug('Loaded proxies from cache', {
          count: this.proxies.length,
          lastUpdate: this.lastUpdate,
        });
      } else {
        this.logger.debug('No cached proxies found');
      }
    } catch (error) {
      this.logger.error('Failed to load proxies from cache', { error });
    }
  }

  /**
   * Fetch proxies from WebShare API
   */
  private async fetchWebShareProxies(): Promise<ProxyInfo[]> {
    if (!this.config.webshare) {
      throw new Error('WebShare configuration not provided');
    }

    const { apiKey, apiUrl } = this.config.webshare;
    
    this.logger.info('Fetching proxies from WebShare API', { apiUrl });

    try {
      const response = await fetch(
        `${apiUrl}proxy/list/?mode=direct&page=1&page_size=100`,
        {
          method: 'GET',
          headers: {
            Authorization: `Token ${apiKey}`,
            'Content-Type': 'application/json',
          },
          signal: AbortSignal.timeout(10000), // 10 second timeout
        }
      );

      if (!response.ok) {
        throw new Error(`WebShare API request failed: ${response.status} ${response.statusText}`);
      }

      const data = await response.json();

      if (!data.results || !Array.isArray(data.results)) {
        throw new Error('Invalid response format from WebShare API');
      }

      // Transform proxy data to ProxyInfo format
      const proxies: ProxyInfo[] = data.results.map(
        (proxy: { username: string; password: string; proxy_address: string; port: number }) => ({
          source: 'webshare',
          protocol: 'http' as const,
          host: proxy.proxy_address,
          port: proxy.port,
          username: proxy.username,
          password: proxy.password,
          isWorking: true, // WebShare provides working proxies
          firstSeen: new Date(),
          lastChecked: new Date(),
        })
      );

      this.logger.info('Successfully fetched proxies from WebShare', {
        count: proxies.length,
        total: data.count || proxies.length,
      });

      this.lastFetchTime = new Date();
      return proxies;
    } catch (error) {
      this.logger.error('Failed to fetch proxies from WebShare', { error });
      throw error;
    }
  }

  /**
   * Refresh proxies from WebShare (public method for manual refresh)
   */
  async refreshProxies(): Promise<void> {
    if (!this.config.enabled || !this.config.webshare) {
      this.logger.warn('Proxy refresh called but WebShare is not configured');
      return;
    }

    try {
      const proxies = await this.fetchWebShareProxies();
      await this.updateProxies(proxies);
    } catch (error) {
      this.logger.error('Failed to refresh proxies', { error });
      throw error;
    }
  }

  /**
   * Get the last time proxies were fetched from WebShare
   */
  getLastFetchTime(): Date | null {
    return this.lastFetchTime;
  }

  /**
   * Initialize the proxy manager
   */
  async initialize(): Promise<void> {
    await this.initializeInternal();

    // Fetch proxies on startup if enabled
    if (this.config.enabled && this.config.webshare) {
      this.logger.info('Proxy fetching is enabled, fetching proxies from WebShare...');
      
      try {
        const proxies = await this.fetchWebShareProxies();
        if (proxies.length === 0) {
          throw new Error('No proxies fetched from WebShare API');
        }
        
        await this.updateProxies(proxies);
        this.logger.info('ProxyManager initialized with fresh proxies', {
          count: proxies.length,
          lastFetchTime: this.lastFetchTime,
        });
      } catch (error) {
        // If proxy fetching is enabled but fails, the service should not start
        this.logger.error('Failed to fetch proxies during initialization', { error });
        throw new Error(`ProxyManager initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
      }
    } else {
      this.logger.info('ProxyManager initialized without fetching proxies (disabled or not configured)');
    }
  }
}

// Export the class as default
export default ProxyManager;
