import { Protocol, ApyData, ApyResult, GroupedApys, BestApyResult, ApyFetcherError } from './types';

export class ApyFetcher {
  private protocols: Protocol[] = [];

  constructor(protocols?: Protocol[]) {
    this.initializeProtocols(protocols || []);
  }

  private initializeProtocols(protocols: Protocol[]): void {
    this.protocols.push(...protocols);
  }

  /**
   * Fetch APYs from all configured protocols
   * @param gracefulDegradation - Continue with other protocols if one fails
   */
  async fetchApys(gracefulDegradation: boolean = true): Promise<ApyData[]> {
    const results = await this.fetchApysWithResults(gracefulDegradation);
    
    // Aggregate all successful results
    const allApys: ApyData[] = [];
    for (const result of results) {
      if (result.success && result.data) {
        allApys.push(...result.data);
      }
    }

    return allApys;
  }

  /**
   * Fetch APYs with detailed results per protocol
   */
  async fetchApysWithResults(gracefulDegradation: boolean = true): Promise<ApyResult[]> {
    const promises = this.protocols.map(async (protocol): Promise<ApyResult> => {
      try {
        const data = await protocol.fetchApys();
        return {
          success: true,
          data,
          protocol: protocol.name,
        };
      } catch (error) {
        const apyError = error instanceof ApyFetcherError 
          ? error 
          : new ApyFetcherError(`Failed to fetch from ${protocol.name}`, protocol.name, error as Error);

        if (!gracefulDegradation) {
          throw apyError;
        }

        return {
          success: false,
          error: apyError,
          protocol: protocol.name,
        };
      }
    });

    return Promise.all(promises);
  }

  /**
   * Get APYs grouped by token symbol
   */
  async getApysByToken(gracefulDegradation: boolean = true): Promise<GroupedApys> {
    const apys = await this.fetchApys(gracefulDegradation);
    const grouped: GroupedApys = {};

    for (const apy of apys) {
      if (!grouped[apy.token_symbol]) {
        grouped[apy.token_symbol] = [];
      }
      grouped[apy.token_symbol].push(apy);
    }

    // Sort each group by APY descending
    for (const symbol in grouped) {
      grouped[symbol].sort((a, b) => b.apy - a.apy);
    }

    return grouped;
  }

  /**
   * Get the best APY for a specific token across all protocols
   */
  async getBestApyForToken(tokenSymbol: string, gracefulDegradation: boolean = true): Promise<BestApyResult | null> {
    const apys = await this.fetchApys(gracefulDegradation);
    const tokenApys = apys.filter(apy => 
      apy.token_symbol.toLowerCase() === tokenSymbol.toLowerCase()
    );

    if (tokenApys.length === 0) {
      return null;
    }

    // Sort by APY descending
    tokenApys.sort((a, b) => b.apy - a.apy);

    return {
      token_symbol: tokenSymbol,
      apy: tokenApys[0].apy,
      network: tokenApys[0].network,
    };
  }

  /**
   * Get all available protocols
   */
  getProtocols(): Protocol[] {
    return [...this.protocols];
  }

  /**
   * Add a custom protocol
   */
  addProtocol(protocol: Protocol): void {
    this.protocols.push(protocol);
  }
} 