import * as plugins from '../plugins.js';
import type { IStockProvider, IProviderConfig, IProviderRegistry } from './interfaces/provider.js';
import type { IStockPrice, IStockQuoteRequest, IStockBatchQuoteRequest, IStockPriceError } from './interfaces/stockprice.js';

interface IProviderEntry {
  provider: IStockProvider;
  config: IProviderConfig;
  lastError?: Error;
  lastErrorTime?: Date;
  successCount: number;
  errorCount: number;
}

interface ICacheEntry {
  price: IStockPrice;
  timestamp: Date;
}

export class StockPriceService implements IProviderRegistry {
  private providers = new Map<string, IProviderEntry>();
  private cache = new Map<string, ICacheEntry>();
  private logger = console;
  
  private cacheConfig = {
    ttl: 60000, // 60 seconds default
    maxEntries: 1000
  };

  constructor(cacheConfig?: { ttl?: number; maxEntries?: number }) {
    if (cacheConfig) {
      this.cacheConfig = { ...this.cacheConfig, ...cacheConfig };
    }
  }

  public register(provider: IStockProvider, config?: IProviderConfig): void {
    const defaultConfig: IProviderConfig = {
      enabled: true,
      priority: provider.priority,
      timeout: 10000,
      retryAttempts: 2,
      retryDelay: 1000
    };

    const mergedConfig = { ...defaultConfig, ...config };
    
    this.providers.set(provider.name, {
      provider,
      config: mergedConfig,
      successCount: 0,
      errorCount: 0
    });

    console.log(`Registered provider: ${provider.name}`);
  }

  public unregister(providerName: string): void {
    this.providers.delete(providerName);
    console.log(`Unregistered provider: ${providerName}`);
  }

  public getProvider(name: string): IStockProvider | undefined {
    return this.providers.get(name)?.provider;
  }

  public getAllProviders(): IStockProvider[] {
    return Array.from(this.providers.values()).map(entry => entry.provider);
  }

  public getEnabledProviders(): IStockProvider[] {
    return Array.from(this.providers.values())
      .filter(entry => entry.config.enabled)
      .sort((a, b) => (b.config.priority || 0) - (a.config.priority || 0))
      .map(entry => entry.provider);
  }

  public async getPrice(request: IStockQuoteRequest): Promise<IStockPrice> {
    const cacheKey = this.getCacheKey(request);
    const cached = this.getFromCache(cacheKey);
    
    if (cached) {
      console.log(`Cache hit for ${request.ticker}`);
      return cached;
    }

    const providers = this.getEnabledProviders();
    if (providers.length === 0) {
      throw new Error('No stock price providers available');
    }

    let lastError: Error | undefined;

    for (const provider of providers) {
      const entry = this.providers.get(provider.name)!;
      
      try {
        const price = await this.fetchWithRetry(
          () => provider.fetchPrice(request),
          entry.config
        );

        entry.successCount++;
        this.addToCache(cacheKey, price);
        console.log(`Successfully fetched ${request.ticker} from ${provider.name}`);
        return price;
      } catch (error) {
        entry.errorCount++;
        entry.lastError = error as Error;
        entry.lastErrorTime = new Date();
        lastError = error as Error;
        
        console.warn(
          `Provider ${provider.name} failed for ${request.ticker}: ${error.message}`
        );
      }
    }

    throw new Error(
      `Failed to fetch price for ${request.ticker} from all providers. Last error: ${lastError?.message}`
    );
  }

  public async getPrices(request: IStockBatchQuoteRequest): Promise<IStockPrice[]> {
    const cachedPrices: IStockPrice[] = [];
    const tickersToFetch: string[] = [];

    // Check cache for each ticker
    for (const ticker of request.tickers) {
      const cacheKey = this.getCacheKey({ ticker, includeExtendedHours: request.includeExtendedHours });
      const cached = this.getFromCache(cacheKey);
      
      if (cached) {
        cachedPrices.push(cached);
      } else {
        tickersToFetch.push(ticker);
      }
    }

    if (tickersToFetch.length === 0) {
      console.log(`All ${request.tickers.length} tickers served from cache`);
      return cachedPrices;
    }

    const providers = this.getEnabledProviders();
    if (providers.length === 0) {
      throw new Error('No stock price providers available');
    }

    let lastError: Error | undefined;
    let fetchedPrices: IStockPrice[] = [];

    for (const provider of providers) {
      const entry = this.providers.get(provider.name)!;
      
      try {
        fetchedPrices = await this.fetchWithRetry(
          () => provider.fetchPrices({ 
            tickers: tickersToFetch, 
            includeExtendedHours: request.includeExtendedHours 
          }),
          entry.config
        );

        entry.successCount++;
        
        // Cache the fetched prices
        for (const price of fetchedPrices) {
          const cacheKey = this.getCacheKey({ 
            ticker: price.ticker, 
            includeExtendedHours: request.includeExtendedHours 
          });
          this.addToCache(cacheKey, price);
        }

        console.log(
          `Successfully fetched ${fetchedPrices.length} prices from ${provider.name}`
        );
        break;
      } catch (error) {
        entry.errorCount++;
        entry.lastError = error as Error;
        entry.lastErrorTime = new Date();
        lastError = error as Error;
        
        console.warn(
          `Provider ${provider.name} failed for batch request: ${error.message}`
        );
      }
    }

    if (fetchedPrices.length === 0 && lastError) {
      throw new Error(
        `Failed to fetch prices from all providers. Last error: ${lastError.message}`
      );
    }

    return [...cachedPrices, ...fetchedPrices];
  }

  public async checkProvidersHealth(): Promise<Map<string, boolean>> {
    const health = new Map<string, boolean>();
    
    for (const [name, entry] of this.providers) {
      if (!entry.config.enabled) {
        health.set(name, false);
        continue;
      }

      try {
        const isAvailable = await entry.provider.isAvailable();
        health.set(name, isAvailable);
      } catch (error) {
        health.set(name, false);
        console.error(`Health check failed for ${name}:`, error);
      }
    }

    return health;
  }

  public getProviderStats(): Map<string, { 
    successCount: number; 
    errorCount: number; 
    lastError?: string;
    lastErrorTime?: Date;
  }> {
    const stats = new Map();
    
    for (const [name, entry] of this.providers) {
      stats.set(name, {
        successCount: entry.successCount,
        errorCount: entry.errorCount,
        lastError: entry.lastError?.message,
        lastErrorTime: entry.lastErrorTime
      });
    }

    return stats;
  }

  public clearCache(): void {
    this.cache.clear();
    console.log('Cache cleared');
  }

  public setCacheTTL(ttl: number): void {
    this.cacheConfig.ttl = ttl;
    console.log(`Cache TTL set to ${ttl}ms`);
  }

  private async fetchWithRetry<T>(
    fetchFn: () => Promise<T>,
    config: IProviderConfig
  ): Promise<T> {
    const maxAttempts = config.retryAttempts || 1;
    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await fetchFn();
      } catch (error) {
        lastError = error as Error;
        
        if (attempt < maxAttempts) {
          const delay = (config.retryDelay || 1000) * attempt;
          console.log(`Retry attempt ${attempt} after ${delay}ms`);
          await plugins.smartdelay.delayFor(delay);
        }
      }
    }

    throw lastError || new Error('Unknown error during fetch');
  }

  private getCacheKey(request: IStockQuoteRequest): string {
    return `${request.ticker}:${request.includeExtendedHours || false}`;
  }

  private getFromCache(key: string): IStockPrice | null {
    const entry = this.cache.get(key);
    
    if (!entry) {
      return null;
    }

    const age = Date.now() - entry.timestamp.getTime();
    if (age > this.cacheConfig.ttl) {
      this.cache.delete(key);
      return null;
    }

    return entry.price;
  }

  private addToCache(key: string, price: IStockPrice): void {
    // Enforce max entries limit
    if (this.cache.size >= this.cacheConfig.maxEntries) {
      // Remove oldest entry
      const oldestKey = this.cache.keys().next().value;
      if (oldestKey) {
        this.cache.delete(oldestKey);
      }
    }

    this.cache.set(key, {
      price,
      timestamp: new Date()
    });
  }
}