/**
 * Central registry for managing operations across multiple providers
 */

import type { Logger, MongoDBClient } from '@stock-bot/types';
import { BaseOperationProvider } from './BaseOperationProvider';
import { OperationTracker } from './OperationTracker';
import type {
  OperationComponentOptions,
  OperationUpdate,
  StaleSymbolOptions,
  BulkOperationUpdate,
  OperationStats,
  OperationConfig
} from './types';

/**
 * Registry that manages multiple operation providers and their trackers
 */
export class OperationRegistry {
  private mongodb: MongoDBClient;
  private logger: Logger;
  private providers: Map<string, BaseOperationProvider> = new Map();
  private trackers: Map<string, OperationTracker> = new Map();
  private initialized = false;

  constructor({ mongodb, logger }: OperationComponentOptions) {
    this.mongodb = mongodb;
    this.logger = logger;
  }

  /**
   * Create registry with a provider already registered
   */
  static async createWithProvider(
    options: OperationComponentOptions,
    provider: BaseOperationProvider
  ): Promise<OperationRegistry> {
    const registry = new OperationRegistry(options);
    await registry.registerProvider(provider);
    return registry;
  }

  /**
   * Register a new provider
   */
  async registerProvider(provider: BaseOperationProvider): Promise<void> {
    const config = provider.getProviderConfig();
    
    if (this.providers.has(config.name)) {
      throw new Error(`Provider '${config.name}' is already registered`);
    }

    this.logger.info(`Registering provider: ${config.name}`);

    // Store provider
    this.providers.set(config.name, provider);

    // Create and initialize tracker
    const tracker = new OperationTracker(
      { mongodb: this.mongodb, logger: this.logger },
      provider
    );
    
    await tracker.initialize();
    this.trackers.set(config.name, tracker);

    // Log provider info
    provider.logInfo();

    this.logger.info(`Provider registered successfully: ${config.name}`);
  }

  /**
   * Initialize all registered providers
   */
  async initialize(): Promise<void> {
    if (this.initialized) {
      return;
    }

    this.logger.info('Initializing operation registry');

    // Initialize is already done during registration
    this.initialized = true;

    this.logger.info('Operation registry initialized', {
      providerCount: this.providers.size,
      providers: Array.from(this.providers.keys())
    });
  }

  /**
   * Get a provider by name
   */
  getProvider(providerName: string): BaseOperationProvider {
    const provider = this.providers.get(providerName);
    if (!provider) {
      throw new Error(`Provider '${providerName}' not found`);
    }
    return provider;
  }

  /**
   * Get a tracker by provider name
   */
  getTracker(providerName: string): OperationTracker {
    const tracker = this.trackers.get(providerName);
    if (!tracker) {
      throw new Error(`Tracker for provider '${providerName}' not found`);
    }
    return tracker;
  }

  /**
   * Update operation status for a symbol
   */
  async updateOperation(
    providerName: string,
    symbol: string,
    operationName: string,
    data: OperationUpdate
  ): Promise<void> {
    const tracker = this.getTracker(providerName);
    await tracker.updateSymbolOperation(symbol, operationName, data);
  }

  /**
   * Bulk update operations
   */
  async bulkUpdateOperations(
    providerName: string,
    updates: Array<{
      symbol: string;
      operation: string;
      data: OperationUpdate;
    }>
  ): Promise<void> {
    const tracker = this.getTracker(providerName);
    await tracker.bulkUpdateSymbolOperations(updates);
  }

  /**
   * Get stale symbols for an operation
   */
  async getStaleSymbols(
    providerName: string,
    operationName: string,
    options?: StaleSymbolOptions
  ): Promise<Array<{ symbol: any; lastRun?: Date; lastSuccess?: Date; operations?: any }>> {
    const tracker = this.getTracker(providerName);
    return tracker.getStaleSymbols(operationName, options);
  }

  /**
   * Get operation statistics
   */
  async getOperationStats(
    providerName: string,
    operationName: string
  ): Promise<OperationStats> {
    const tracker = this.getTracker(providerName);
    return tracker.getOperationStats(operationName);
  }

  /**
   * Get all registered providers
   */
  getProviders(): string[] {
    return Array.from(this.providers.keys());
  }

  /**
   * Get all operations for a provider
   */
  getProviderOperations(providerName: string): OperationConfig[] {
    const provider = this.getProvider(providerName);
    return provider.getOperations();
  }

  /**
   * Get all operations across all providers
   */
  getAllOperations(): Array<{ provider: string; operations: OperationConfig[] }> {
    return Array.from(this.providers.entries()).map(([name, provider]) => ({
      provider: name,
      operations: provider.getOperations()
    }));
  }

  /**
   * Check if a provider is registered
   */
  hasProvider(providerName: string): boolean {
    return this.providers.has(providerName);
  }

  /**
   * Get statistics for all operations of a provider
   */
  async getProviderStats(providerName: string): Promise<{
    provider: string;
    operations: Array<{
      name: string;
      stats: OperationStats;
    }>;
  }> {
    const provider = this.getProvider(providerName);
    const tracker = this.getTracker(providerName);
    const operations = provider.getOperations();

    const operationStats = await Promise.all(
      operations.map(async (op) => ({
        name: op.name,
        stats: await tracker.getOperationStats(op.name)
      }))
    );

    return {
      provider: providerName,
      operations: operationStats
    };
  }

  /**
   * Get summary statistics across all providers
   */
  async getGlobalStats(): Promise<{
    totalProviders: number;
    totalOperations: number;
    providerStats: Array<{
      provider: string;
      operationCount: number;
      totalSymbols: number;
      totalProcessed: number;
    }>;
  }> {
    const providerStats = await Promise.all(
      Array.from(this.providers.keys()).map(async (providerName) => {
        const provider = this.getProvider(providerName);
        const tracker = this.getTracker(providerName);
        const operations = provider.getOperations();
        
        // Get stats for first operation to get total symbols
        const firstOpStats = operations.length > 0 
          ? await tracker.getOperationStats(operations[0].name)
          : { totalSymbols: 0, processedSymbols: 0 };

        // Sum processed symbols across all operations
        let totalProcessed = 0;
        for (const op of operations) {
          const stats = await tracker.getOperationStats(op.name);
          totalProcessed += stats.processedSymbols;
        }

        return {
          provider: providerName,
          operationCount: operations.length,
          totalSymbols: firstOpStats.totalSymbols,
          totalProcessed: totalProcessed
        };
      })
    );

    return {
      totalProviders: this.providers.size,
      totalOperations: Array.from(this.providers.values())
        .reduce((sum, p) => sum + p.getOperations().length, 0),
      providerStats
    };
  }

  /**
   * Find operations by type across all providers
   */
  findOperationsByType(type: string): Array<{
    provider: string;
    operation: OperationConfig;
  }> {
    const results: Array<{ provider: string; operation: OperationConfig }> = [];

    for (const [providerName, provider] of this.providers) {
      const operations = provider.getOperations()
        .filter(op => op.type === type);
      
      operations.forEach(operation => {
        results.push({ provider: providerName, operation });
      });
    }

    return results;
  }

  /**
   * Helper method for scheduling operations
   */
  async getSymbolsForScheduling(
    providerName: string,
    operationName: string,
    options?: StaleSymbolOptions
  ): Promise<Array<{ symbol: string; metadata?: any }>> {
    const provider = this.getProvider(providerName);
    const tracker = this.getTracker(providerName);
    const { collectionName, symbolField } = provider.getProviderConfig();

    // Get stale symbols
    const staleSymbols = await tracker.getStaleSymbols(operationName, options);

    if (staleSymbols.length === 0) {
      return [];
    }

    // Get full symbol data for scheduling
    const symbols = await this.mongodb.find(collectionName, {
      [symbolField]: { $in: staleSymbols }
    }, {
      projection: {
        [symbolField]: 1,
        symbol: 1,
        exchange: 1,
        // Include any other fields needed for scheduling
      }
    });

    return symbols.map(doc => ({
      symbol: doc[symbolField],
      metadata: {
        symbol: doc.symbol,
        exchange: doc.exchange,
        // Include other metadata as needed
      }
    }));
  }
}