import type { FastifyBaseLogger } from 'fastify';
import { DuckDBClient } from './duckdb-client.js';

/**
 * Iceberg client configuration
 *
 * Supports separate catalog/warehouse configurations for:
 * 1. Conversation data (catalogUri + namespace, typically 'gateway')
 * 2. OHLC/Trading data (ohlcCatalogUri + ohlcNamespace, typically 'trading')
 *
 * This allows for:
 * - Different S3 buckets/warehouses per data type
 * - Different retention policies
 * - Independent scaling and management
 * - Cost optimization (e.g., cheaper storage class for old conversations)
 */
export interface IcebergConfig {
  // Conversation/Gateway data catalog
  catalogUri: string;
  namespace: string;

  // S3 configuration for conversation data
  s3Endpoint?: string;
  s3AccessKey?: string;
  s3SecretKey?: string;

  // OHLC/Trading data catalog (can be same or different from conversation catalog)
  ohlcCatalogUri?: string;
  ohlcNamespace?: string;

  // S3 bucket for conversation cold storage (Parquet flush at session end)
  conversationsBucket?: string;
}

/**
 * Message record for Iceberg storage
 */
export interface IcebergMessage {
  id: string;
  user_id: string;
  session_id: string;
  role: 'user' | 'assistant' | 'system' | 'workspace';
  content: string;
  metadata: string; // JSON string
  timestamp: number; // nanoseconds
}

/**
 * Blob record for Iceberg storage (images, audio, etc.)
 */
export interface IcebergBlob {
  id: string;
  user_id: string;
  session_id: string;
  message_id: string;
  blob_type: string;
  mime_type: string;
  data: string; // base64
  caption: string | null;
  timestamp: number; // microseconds
}

/**
 * Checkpoint record for Iceberg storage
 */
export interface IcebergCheckpoint {
  user_id: string;
  session_id: string;
  checkpoint_id: string;
  checkpoint_data: string; // JSON string
  metadata: string; // JSON string
  timestamp: number; // nanoseconds
}

/**
 * Iceberg REST client wrapper for durable storage
 *
 * Architecture:
 * - Uses DuckDB with Iceberg extension for querying Parquet data
 * - Supports SEPARATE catalogs/warehouses for conversation vs OHLC data
 * - Writes are handled by Flink via Kafka; this client is READ-ONLY
 *
 * Data separation:
 * 1. Conversation data: catalogUri + namespace (e.g., http://catalog:8181 + 'gateway')
 *    - Tables: conversations, checkpoints
 *    - Can use different warehouse/S3 bucket in the future
 *
 * 2. OHLC/Trading data: ohlcCatalogUri + ohlcNamespace (e.g., http://catalog:8181 + 'trading')
 *    - Tables: ohlc, symbol_metadata
 *    - Can use different warehouse/S3 bucket for cost optimization
 *
 * To use separate warehouses in production:
 * 1. Deploy two Iceberg REST catalog instances (or configure multi-warehouse)
 * 2. Point catalogUri to conversations warehouse
 * 3. Point ohlcCatalogUri to trading warehouse
 * 4. Update Flink configuration to write to the correct catalogs
 */
export class IcebergClient {
  private duckdb: DuckDBClient;
  private logger: FastifyBaseLogger;
  private namespace: string;
  private ohlcNamespace: string;

  constructor(config: IcebergConfig, logger: FastifyBaseLogger) {
    this.logger = logger;
    this.namespace = config.namespace;
    this.ohlcNamespace = config.ohlcNamespace || 'trading';

    // Initialize DuckDB client for querying Iceberg tables
    // DuckDB will query tables from the appropriate catalog based on the data type
    this.duckdb = new DuckDBClient(
      {
        catalogUri: config.catalogUri,
        namespace: config.namespace,
        ohlcCatalogUri: config.ohlcCatalogUri,
        ohlcNamespace: config.ohlcNamespace,
        s3Endpoint: config.s3Endpoint,
        s3AccessKey: config.s3AccessKey,
        s3SecretKey: config.s3SecretKey,
        conversationsBucket: config.conversationsBucket,
      },
      logger
    );

    this.logger.info({
      catalogUri: config.catalogUri,
      namespace: this.namespace,
      ohlcCatalogUri: config.ohlcCatalogUri || config.catalogUri,
      ohlcNamespace: this.ohlcNamespace,
    }, 'Iceberg client initialized with separate conversation and OHLC catalogs');
  }

  /**
   * Query messages from gateway.conversations table
   */
  async queryMessages(
    userId: string,
    sessionId: string,
    options?: {
      startTime?: number;
      endTime?: number;
      limit?: number;
    }
  ): Promise<IcebergMessage[]> {
    return this.duckdb.queryMessages(userId, sessionId, options);
  }

  /**
   * Query checkpoint from gateway.checkpoints table
   */
  async queryCheckpoint(
    userId: string,
    sessionId: string,
    checkpointId?: string
  ): Promise<IcebergCheckpoint | null> {
    return this.duckdb.queryCheckpoint(userId, sessionId, checkpointId);
  }

  /**
   * Append a batch of conversation messages as a Parquet file in S3.
   * Called once per session at session end.
   */
  async appendMessages(
    userId: string,
    sessionId: string,
    messages: IcebergMessage[]
  ): Promise<void> {
    return this.duckdb.appendMessages(userId, sessionId, messages);
  }

  /**
   * Append blobs for one assistant turn as a Parquet file in S3.
   */
  async appendBlobs(
    userId: string,
    sessionId: string,
    messageId: string,
    blobs: IcebergBlob[]
  ): Promise<void> {
    return this.duckdb.appendBlobs(userId, sessionId, messageId, blobs);
  }

  /**
   * Query blobs from S3/Iceberg, optionally filtered to specific blob IDs.
   */
  async queryBlobs(userId: string, sessionId: string, blobIds?: string[]): Promise<IcebergBlob[]> {
    return this.duckdb.queryBlobs(userId, sessionId, blobIds);
  }

  /**
   * Get table metadata
   */
  async getTableMetadata(tableName: string): Promise<any> {
    try {
      const tableId = `${this.namespace}.${tableName}`;

      // Note: iceberg-js provides catalog operations
      // For actual data queries, you'd need to:
      // 1. Get table metadata
      // 2. Find data file locations
      // 3. Query Parquet files from S3

      this.logger.info({ table: tableId }, 'Getting table metadata');

      // TODO: Implement table metadata query via REST API
      return null;
    } catch (error) {
      this.logger.error({ error, tableName }, 'Failed to get table metadata');
      throw error;
    }
  }

  /**
   * List tables in namespace
   */
  async listTables(): Promise<string[]> {
    try {
      this.logger.info({ namespace: this.namespace }, 'Listing tables');

      // TODO: Use iceberg-js to list tables
      // const tables = await this.client.listTables(this.namespace);
      // return tables.map(t => t.name);

      return [];
    } catch (error) {
      this.logger.error({ error }, 'Failed to list tables');
      throw error;
    }
  }

  /**
   * Check if table exists
   */
  async tableExists(tableName: string): Promise<boolean> {
    try {
      const tables = await this.listTables();
      return tables.includes(tableName);
    } catch (error) {
      this.logger.error({ error, tableName }, 'Failed to check table existence');
      return false;
    }
  }

  /**
   * Query OHLC data from trading.ohlc table
   */
  async queryOHLC(
    ticker: string,
    period_seconds: number,
    start_time: bigint,  // nanoseconds
    end_time: bigint     // nanoseconds
  ): Promise<any[]> {
    return this.duckdb.queryOHLC(ticker, period_seconds, start_time, end_time);
  }

  /**
   * Check if OHLC data exists for the given parameters
   */
  async hasOHLCData(
    ticker: string,
    period_seconds: number,
    start_time: bigint,
    end_time: bigint
  ): Promise<boolean> {
    return this.duckdb.hasOHLCData(ticker, period_seconds, start_time, end_time);
  }

  /**
   * Find missing OHLC data ranges
   */
  async findMissingOHLCRanges(
    ticker: string,
    period_seconds: number,
    start_time: bigint,
    end_time: bigint
  ): Promise<Array<[bigint, bigint]>> {
    return this.duckdb.findMissingOHLCRanges(ticker, period_seconds, start_time, end_time);
  }

  /**
   * Query all symbols from symbol_metadata table
   */
  async queryAllSymbols(): Promise<any[]> {
    return this.duckdb.queryAllSymbols();
  }
}

/**
 * Note on Iceberg Writes:
 *
 * For appending messages and checkpoints to Iceberg, we have two options:
 *
 * 1. **Via Kafka + Flink** (Recommended):
 *    - Gateway writes to Kafka topics (gateway_messages, gateway_checkpoints)
 *    - Flink consumes and writes to Iceberg with proper partitioning
 *    - Benefits: Proven architecture, handles backpressure, deduplication
 *
 * 2. **Direct REST Catalog Write**:
 *    - Use Iceberg REST API to commit new data files
 *    - More complex: need to create Parquet files, upload to S3, commit transaction
 *    - Library like parquet-wasm could help
 *
 * For now, recommend Option 1 (Kafka + Flink) for consistency with existing architecture.
 */
