import { getLogger } from '@stock-bot/logger';
import type { QueryResult, TableNames, TimeRange } from './types';

// Interface to avoid circular dependency
interface QuestDBClientInterface {
  query<T = any>(sql: string, params?: any[]): Promise<QueryResult<T>>;
}

/**
 * QuestDB Query Builder
 *
 * Provides a fluent interface for building optimized time-series queries
 * with support for QuestDB-specific functions and optimizations.
 */
export class QuestDBQueryBuilder {
  private readonly logger: ReturnType<typeof getLogger>;
  private query!: {
    select: string[];
    from: string;
    where: string[];
    groupBy: string[];
    orderBy: string[];
    limit?: number;
    sampleBy?: string;
    latestBy?: string[];
    timeRange?: TimeRange;
  };
  constructor(private readonly client: QuestDBClientInterface) {
    this.logger = getLogger('questdb-query-builder');
    this.reset();
  }

  /**
   * Reset the query builder
   */
  private reset(): QuestDBQueryBuilder {
    this.query = {
      select: [],
      from: '',
      where: [],
      groupBy: [],
      orderBy: [],
      sampleBy: undefined,
      latestBy: undefined,
      timeRange: undefined,
    };
    return this;
  }
  /**
   * Start a new query
   */
  public static create(client: QuestDBClientInterface): QuestDBQueryBuilder {
    return new QuestDBQueryBuilder(client);
  }

  /**
   * Select columns
   */
  public select(...columns: string[]): QuestDBQueryBuilder {
    this.query.select.push(...columns);
    return this;
  }

  /**
   * Select with aggregation functions
   */
  public selectAgg(aggregations: Record<string, string>): QuestDBQueryBuilder {
    Object.entries(aggregations).forEach(([alias, expression]) => {
      this.query.select.push(`${expression} as ${alias}`);
    });
    return this;
  }

  /**
   * From table
   */
  public from(table: TableNames | string): QuestDBQueryBuilder {
    this.query.from = table;
    return this;
  }

  /**
   * Where condition
   */
  public where(condition: string): QuestDBQueryBuilder {
    this.query.where.push(condition);
    return this;
  }

  /**
   * Where symbol equals
   */
  public whereSymbol(symbol: string): QuestDBQueryBuilder {
    this.query.where.push(`symbol = '${symbol}'`);
    return this;
  }

  /**
   * Where symbols in list
   */
  public whereSymbolIn(symbols: string[]): QuestDBQueryBuilder {
    const symbolList = symbols.map(s => `'${s}'`).join(', ');
    this.query.where.push(`symbol IN (${symbolList})`);
    return this;
  }

  /**
   * Where exchange equals
   */
  public whereExchange(exchange: string): QuestDBQueryBuilder {
    this.query.where.push(`exchange = '${exchange}'`);
    return this;
  }

  /**
   * Time range filter
   */
  public whereTimeRange(startTime: Date, endTime: Date): QuestDBQueryBuilder {
    this.query.timeRange = { startTime, endTime };
    this.query.where.push(
      `timestamp >= '${startTime.toISOString()}' AND timestamp <= '${endTime.toISOString()}'`
    );
    return this;
  }

  /**
   * Last N hours
   */
  public whereLastHours(hours: number): QuestDBQueryBuilder {
    this.query.where.push(`timestamp > dateadd('h', -${hours}, now())`);
    return this;
  }

  /**
   * Last N days
   */
  public whereLastDays(days: number): QuestDBQueryBuilder {
    this.query.where.push(`timestamp > dateadd('d', -${days}, now())`);
    return this;
  }

  /**
   * Group by columns
   */
  public groupBy(...columns: string[]): QuestDBQueryBuilder {
    this.query.groupBy.push(...columns);
    return this;
  }

  /**
   * Order by column
   */
  public orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): QuestDBQueryBuilder {
    this.query.orderBy.push(`${column} ${direction}`);
    return this;
  }

  /**
   * Order by timestamp descending (most recent first)
   */
  public orderByTimeDesc(): QuestDBQueryBuilder {
    this.query.orderBy.push('timestamp DESC');
    return this;
  }

  /**
   * Limit results
   */
  public limit(count: number): QuestDBQueryBuilder {
    this.query.limit = count;
    return this;
  }

  /**
   * Sample by time interval (QuestDB specific)
   */
  public sampleBy(interval: string): QuestDBQueryBuilder {
    this.query.sampleBy = interval;
    return this;
  }

  /**
   * Latest by columns (QuestDB specific)
   */
  public latestBy(...columns: string[]): QuestDBQueryBuilder {
    this.query.latestBy = columns;
    return this;
  }

  /**
   * Build and execute the query
   */
  public async execute<T = any>(): Promise<QueryResult<T>> {
    const sql = this.build();
    this.logger.debug('Executing query', { sql });

    try {
      const result = await this.client.query<T>(sql);
      this.reset(); // Reset for next query
      return result;
    } catch (error) {
      this.logger.error('Query execution failed', { sql, error });
      this.reset(); // Reset even on error
      throw error;
    }
  }

  /**
   * Build the SQL query string
   */
  public build(): string {
    if (!this.query.from) {
      throw new Error('FROM clause is required');
    }

    if (this.query.select.length === 0) {
      this.query.select.push('*');
    }

    let sql = `SELECT ${this.query.select.join(', ')} FROM ${this.query.from}`;

    // Add WHERE clause
    if (this.query.where.length > 0) {
      sql += ` WHERE ${this.query.where.join(' AND ')}`;
    }

    // Add LATEST BY (QuestDB specific - must come before GROUP BY)
    if (this.query.latestBy && this.query.latestBy.length > 0) {
      sql += ` LATEST BY ${this.query.latestBy.join(', ')}`;
    }

    // Add SAMPLE BY (QuestDB specific)
    if (this.query.sampleBy) {
      sql += ` SAMPLE BY ${this.query.sampleBy}`;
    }

    // Add GROUP BY
    if (this.query.groupBy.length > 0) {
      sql += ` GROUP BY ${this.query.groupBy.join(', ')}`;
    }

    // Add ORDER BY
    if (this.query.orderBy.length > 0) {
      sql += ` ORDER BY ${this.query.orderBy.join(', ')}`;
    }

    // Add LIMIT
    if (this.query.limit) {
      sql += ` LIMIT ${this.query.limit}`;
    }

    return sql;
  }

  /**
   * Get the built query without executing
   */
  public toSQL(): string {
    return this.build();
  }

  // Predefined query methods for common use cases
  /**
   * Get latest OHLCV data for symbols
   */
  public static latestOHLCV(
    client: QuestDBClientInterface,
    symbols: string[],
    exchange?: string
  ): QuestDBQueryBuilder {
    const builder = QuestDBQueryBuilder.create(client)
      .select('symbol', 'timestamp', 'open', 'high', 'low', 'close', 'volume')
      .from('ohlcv_data')
      .whereSymbolIn(symbols)
      .latestBy('symbol')
      .orderByTimeDesc();

    if (exchange) {
      builder.whereExchange(exchange);
    }

    return builder;
  }
  /**
   * Get OHLCV data with time sampling
   */
  public static ohlcvTimeSeries(
    client: QuestDBClientInterface,
    symbol: string,
    interval: string,
    hours: number = 24
  ): QuestDBQueryBuilder {
    return QuestDBQueryBuilder.create(client)
      .selectAgg({
        first_open: 'first(open)',
        max_high: 'max(high)',
        min_low: 'min(low)',
        last_close: 'last(close)',
        sum_volume: 'sum(volume)',
      })
      .from('ohlcv_data')
      .whereSymbol(symbol)
      .whereLastHours(hours)
      .sampleBy(interval)
      .orderByTimeDesc();
  }
  /**
   * Get market analytics data
   */
  public static marketAnalytics(
    client: QuestDBClientInterface,
    symbols: string[],
    hours: number = 1
  ): QuestDBQueryBuilder {
    return QuestDBQueryBuilder.create(client)
      .select(
        'symbol',
        'timestamp',
        'rsi',
        'macd',
        'bollinger_upper',
        'bollinger_lower',
        'volume_sma'
      )
      .from('market_analytics')
      .whereSymbolIn(symbols)
      .whereLastHours(hours)
      .orderBy('symbol')
      .orderByTimeDesc();
  }
  /**
   * Get performance metrics for a time range
   */
  public static performanceMetrics(
    client: QuestDBClientInterface,
    startTime: Date,
    endTime: Date
  ): QuestDBQueryBuilder {
    return QuestDBQueryBuilder.create(client)
      .selectAgg({
        total_trades: 'count(*)',
        avg_response_time: 'avg(response_time)',
        max_response_time: 'max(response_time)',
        error_rate: 'sum(case when success = false then 1 else 0 end) * 100.0 / count(*)',
      })
      .from('performance_metrics')
      .whereTimeRange(startTime, endTime)
      .sampleBy('1m');
  }
  /**
   * Get trade execution data
   */
  public static tradeExecutions(
    client: QuestDBClientInterface,
    symbol?: string,
    hours: number = 24
  ): QuestDBQueryBuilder {
    const builder = QuestDBQueryBuilder.create(client)
      .select('symbol', 'timestamp', 'side', 'quantity', 'price', 'execution_time')
      .from('trade_executions')
      .whereLastHours(hours)
      .orderByTimeDesc();

    if (symbol) {
      builder.whereSymbol(symbol);
    }

    return builder;
  }
}
