/**
 * ZMQ Protocol encoding/decoding using Protobuf
 *
 * Protocol format (as defined in protobuf/ingestor.proto):
 * Frame 1: [1 byte: protocol version]
 * Frame 2: [1 byte: message type ID][N bytes: protobuf message]
 *
 * For PUB/SUB: [topic frame][version frame][message frame]
 */

import protobuf from 'protobufjs';
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import type {
  SubmitHistoricalRequest,
  SubmitResponse,
  HistoryReadyNotification,
  SubmitStatus,
  NotificationStatus,
} from '../types/ohlc.js';

export const OHLC_BAR_TOPIC_PATTERN = /^(.+)\|ohlc:(\d+)$/;

/** Decoded realtime OHLC bar received from the XPUB market data stream */
export interface RealtimeBar {
  topic: string;       // e.g., "BTC/USDT.BINANCE|ohlc:60"
  ticker: string;      // e.g., "BTC/USDT.BINANCE"
  periodSeconds: number;
  /** Window open time in nanoseconds since epoch */
  timestamp: bigint;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
}

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

/**
 * Protocol constants
 */
export const PROTOCOL_VERSION = 0x01;

export enum MessageType {
  SUBMIT_HISTORICAL_REQUEST = 0x10,
  SUBMIT_RESPONSE = 0x11,
  HISTORY_READY_NOTIFICATION = 0x12,
}

// Load protobuf types at runtime (same pattern as ingestor)
// Proto files are copied to /app/protobuf/ in the Docker image
const protoDir = join(__dirname, '../..', 'protobuf');
const root = new protobuf.Root();

// Load proto files
const ingestorProto = readFileSync(join(protoDir, 'ingestor.proto'), 'utf8');
const ohlcProto = readFileSync(join(protoDir, 'ohlc.proto'), 'utf8');
protobuf.parse(ingestorProto, root);
protobuf.parse(ohlcProto, root);

// Export message types
const SubmitHistoricalRequestType = root.lookupType('SubmitHistoricalRequest');
const SubmitResponseType = root.lookupType('SubmitResponse');
const HistoryReadyNotificationType = root.lookupType('HistoryReadyNotification');
const OHLCType = root.lookupType('OHLC');

/**
 * Encode SubmitHistoricalRequest to ZMQ frames
 *
 * Returns: [version_frame, message_frame]
 */
export function encodeSubmitHistoricalRequest(req: SubmitHistoricalRequest): Buffer[] {
  const versionFrame = Buffer.from([PROTOCOL_VERSION]);

  // Convert to protobuf-compatible format (pbjs uses camelCase)
  // Note: protobufjs handles bigint/number conversion automatically for uint64
  const protoMessage = {
    requestId: req.request_id,
    ticker: req.ticker,
    startTime: Number(req.start_time),  // Convert bigint to number for protobuf
    endTime: Number(req.end_time),
    periodSeconds: req.period_seconds,
    limit: req.limit,
    clientId: req.client_id,
  };

  // Encode as protobuf
  const message = SubmitHistoricalRequestType.create(protoMessage);
  const payloadBuffer = SubmitHistoricalRequestType.encode(message).finish();

  const messageFrame = Buffer.concat([
    Buffer.from([MessageType.SUBMIT_HISTORICAL_REQUEST]),
    Buffer.from(payloadBuffer),
  ]);

  return [versionFrame, messageFrame];
}

/**
 * Decode SubmitResponse from ZMQ frames
 *
 * Input: [version_frame, message_frame]
 */
export function decodeSubmitResponse(frames: Buffer[]): SubmitResponse {
  try {
    if (frames.length < 2) {
      throw new Error(`Expected 2 frames, got ${frames.length}`);
    }

    const versionFrame = frames[0];
    const messageFrame = frames[1];

    // Validate version
    if (versionFrame[0] !== PROTOCOL_VERSION) {
      throw new Error(`Unsupported protocol version: ${versionFrame[0]}`);
    }

    // Validate message type
    const messageType = messageFrame[0];
    if (messageType !== MessageType.SUBMIT_RESPONSE) {
      throw new Error(`Expected SUBMIT_RESPONSE (0x11), got 0x${messageType.toString(16)}`);
    }

    // Decode protobuf payload
    const payloadBuffer = messageFrame.slice(1);
    const decoded = SubmitResponseType.decode(payloadBuffer);
    const payload = SubmitResponseType.toObject(decoded, {
      longs: String,
      enums: Number,  // Keep enums as numbers for comparison
      defaults: true,
    });

    return {
      request_id: payload.requestId,
      status: payload.status as SubmitStatus,
      error_message: payload.errorMessage || undefined,
      notification_topic: payload.notificationTopic,
    };
  } catch (error) {
    console.error('Error decoding SubmitResponse:', error);
    console.error('Frame count:', frames.length);
    if (frames.length >= 2) {
      console.error('Version frame:', frames[0].toString('hex'));
      console.error('Message frame (first 100 bytes):', frames[1].slice(0, 100).toString('hex'));
    }
    throw error;
  }
}

/**
 * Decode HistoryReadyNotification from ZMQ frames
 *
 * Input: [topic_frame, version_frame, message_frame] (for SUB socket)
 */
export function decodeHistoryReadyNotification(frames: Buffer[]): HistoryReadyNotification {
  if (frames.length < 3) {
    throw new Error(`Expected 3 frames (topic, version, message), got ${frames.length}`);
  }

  const versionFrame = frames[1];
  const messageFrame = frames[2];

  // Validate version
  if (versionFrame[0] !== PROTOCOL_VERSION) {
    throw new Error(`Unsupported protocol version: ${versionFrame[0]}`);
  }

  // Validate message type
  const messageType = messageFrame[0];
  if (messageType !== MessageType.HISTORY_READY_NOTIFICATION) {
    throw new Error(`Expected HISTORY_READY_NOTIFICATION (0x12), got 0x${messageType.toString(16)}`);
  }

  // Decode protobuf payload
  const payloadBuffer = messageFrame.slice(1);
  const decoded = HistoryReadyNotificationType.decode(payloadBuffer);
  const payload = HistoryReadyNotificationType.toObject(decoded, {
    longs: String,
    enums: Number,  // Keep enums as numbers for comparison
    defaults: true,
  });

  return {
    request_id: payload.requestId,
    ticker: payload.ticker,
    period_seconds: payload.periodSeconds,
    start_time: BigInt(payload.startTime),
    end_time: BigInt(payload.endTime),
    status: payload.status as NotificationStatus,
    error_message: payload.errorMessage || undefined,
    iceberg_namespace: payload.icebergNamespace,
    iceberg_table: payload.icebergTable,
    row_count: payload.rowCount,
    completed_at: BigInt(payload.completedAt),
  };
}

/**
 * Decode a realtime OHLC bar from ZMQ SUB frames.
 * Frame layout: [topic][version][0x04 OHLC type + OHLC protobuf bytes]
 *
 * Returns null if the topic doesn't match the realtime bar pattern or decoding fails.
 */
export function decodeRealtimeBar(frames: Buffer[]): RealtimeBar | null {
  if (frames.length < 3) return null;

  const topic = frames[0].toString();
  const match = OHLC_BAR_TOPIC_PATTERN.exec(topic);
  if (!match) return null;

  const ticker = match[1];
  const periodSeconds = parseInt(match[2], 10);

  const messageFrame = frames[2];
  if (messageFrame[0] !== 0x04) return null; // Must be OHLC type

  const payloadBuffer = messageFrame.slice(1);
  const decoded = OHLCType.decode(payloadBuffer);
  const ohlc = OHLCType.toObject(decoded, { longs: String, defaults: true });

  return {
    topic,
    ticker,
    periodSeconds,
    timestamp: BigInt(ohlc.timestamp ?? '0'),
    open: Number(ohlc.open ?? 0),
    high: Number(ohlc.high ?? 0),
    low: Number(ohlc.low ?? 0),
    close: Number(ohlc.close ?? 0),
    volume: Number(ohlc.volume ?? 0),
  };
}
