/**
 * CheddarFlow WebSocket Service
 * Connects to CheddarFlow's live option flow WebSocket and stores data in database
 * 
 * Protocol: graphql-transport-ws
 * URL: wss://app.cheddarflow.com/api/ws
 */

import WebSocket from 'ws';
import { rawQuery } from '../db.js';
import dotenv from 'dotenv';

dotenv.config();

const CHEDDARFLOW_WS_URL = 'wss://app.cheddarflow.com/api/ws';
const CHEDDARFLOW_API_URL = 'https://app.cheddarflow.com/api';
const CHEDDARFLOW_ORIGIN = 'https://dash.cheddarflow.com';

// Get fetch function (Node.js 18+ has native fetch)
async function getFetch() {
  if (typeof globalThis.fetch !== 'undefined') {
    return globalThis.fetch;
  }
  
  try {
    const nodeFetch = await import('node-fetch');
    return nodeFetch.default || nodeFetch;
  } catch (e) {
    throw new Error('Fetch is not available. Please install node-fetch or use Node.js 18+');
  }
}

// GraphQL Transport WS message types
const GQL_CONNECTION_INIT = 'connection_init';
const GQL_CONNECTION_ACK = 'connection_ack';
const GQL_START = 'start';
const GQL_SUBSCRIBE = 'subscribe';
const GQL_NEXT = 'next';
const GQL_DATA = 'data';
const GQL_ERROR = 'error';
const GQL_COMPLETE = 'complete';
const GQL_CONNECTION_ERROR = 'connection_error';

class CheddarFlowWebSocketClient {
  constructor(options = {}) {
    this.url = options.url || CHEDDARFLOW_WS_URL;
    this.origin = options.origin || CHEDDARFLOW_ORIGIN;
    this.session = options.session || process.env.CHEDDARFLOW_SESSION || null;
    this.token = options.token || process.env.CHEDDARFLOW_TOKEN || null;
    this.cookies = options.cookies || process.env.CHEDDARFLOW_COOKIES || null;
    this.ws = null;
    this.reconnectInterval = options.reconnectInterval || 5000;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = options.maxReconnectAttempts || 10;
    this.isConnected = false;
    this.subscriptionId = null;
    this.onDataCallback = options.onData || null;
    this.onErrorCallback = options.onError || null;
    this.autoReconnect = options.autoReconnect !== false;
    this.messageQueue = [];
  }

  /**
   * Connect to CheddarFlow WebSocket
   */
  connect() {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      console.log('📡 CheddarFlow: Already connected');
      return;
    }

    console.log(`📡 CheddarFlow: Connecting to ${this.url}...`);

    try {
      const headers = {
        'Origin': this.origin,
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
        'Accept-Language': 'en-US,en;q=0.9,te;q=0.8,fr;q=0.7',
        'Cache-Control': 'no-cache',
        'Pragma': 'no-cache',
      };

      // Add cookies if provided (required for authentication)
      if (this.cookies) {
        headers['Cookie'] = this.cookies;
      }

      // Add Authorization header with token if provided
      if (this.token && !this.cookies) {
        headers['Authorization'] = `Bearer ${this.token}`;
      }

      this.ws = new WebSocket(this.url, {
        headers,
        protocol: 'graphql-transport-ws',
      });

      this.setupEventHandlers();
    } catch (error) {
      console.error('📡 CheddarFlow: Connection error:', error);
      this.handleReconnect();
    }
  }

  /**
   * Setup WebSocket event handlers
   */
  setupEventHandlers() {
    this.ws.on('open', () => {
      console.log('📡 CheddarFlow: WebSocket opened');
      this.reconnectAttempts = 0;
      this.sendConnectionInit();
    });

    this.ws.on('message', (data) => {
      try {
        const message = JSON.parse(data.toString());
        this.handleMessage(message);
      } catch (error) {
        console.error('📡 CheddarFlow: Error parsing message:', error);
      }
    });

    this.ws.on('error', (error) => {
      console.error('📡 CheddarFlow: WebSocket error:', error.message);
      this.isConnected = false;
      if (this.onErrorCallback) {
        this.onErrorCallback(error);
      }
    });

    this.ws.on('close', (code, reason) => {
      console.log(`📡 CheddarFlow: WebSocket closed (code: ${code}, reason: ${reason?.toString() || 'none'})`);
      this.isConnected = false;
      this.subscriptionId = null;
      
      if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
        this.handleReconnect();
      } else if (this.reconnectAttempts >= this.maxReconnectAttempts) {
        console.error('📡 CheddarFlow: Max reconnection attempts reached');
      }
    });
  }

  /**
   * Send connection initialization message
   * Includes authentication tokens (session and JWT token) if provided
   */
  sendConnectionInit() {
    const payload = {};
    
    // Add authentication if available
    if (this.session) {
      payload.session = this.session;
    }
    
    if (this.token) {
      payload.token = this.token;
    }
    
    // Warn if authentication is missing
    if (!this.cookies && (!this.session || !this.token)) {
      console.warn('📡 CheddarFlow: Missing authentication. Connection may fail.');
      console.warn('📡 CheddarFlow: Set CHEDDARFLOW_COOKIES (recommended) or CHEDDARFLOW_SESSION + CHEDDARFLOW_TOKEN in .env file');
    }

    const message = {
      type: GQL_CONNECTION_INIT,
      payload
    };
    
    this.send(message);
    console.log('📡 CheddarFlow: Sent connection_init with', 
      this.session ? 'session' : 'no session',
      this.token ? 'token' : 'no token'
    );
  }

  /**
   * Start GraphQL subscription for option flow events
   * 
   * Uses the OnOptionsFeedEvents subscription which provides real-time option flow data.
   * The feed ID/topic can be configured via environment variable or defaults to a common value.
   */
  startSubscription() {
    if (!this.isConnected) {
      console.warn('📡 CheddarFlow: Cannot start subscription - not connected');
      return;
    }

    // Feed ID/topic - this appears to be a view/feed identifier
    // You may need to get this from the CheddarFlow UI or it might be user-specific
    const feedId = process.env.CHEDDARFLOW_FEED_ID || process.env.CHEDDARFLOW_FEED_TOPIC || '1612407366';
    const feedTopic = process.env.CHEDDARFLOW_FEED_TOPIC || feedId;

    const subscriptionQuery = `
      subscription OnOptionsFeedEvents($id: ID!, $topic: String!) {
        optionsFeedEvents(id: $id, topic: $topic) {
          ... on OptionsFeedEvent {
            eventOperation
            eventData {
              aggregate {
                ... on OptionsFlowAggregate {
                  indicators {
                    pcr {
                      sizePcr
                      __typename
                    }
                    sentiment {
                      strength
                      trend
                      __typename
                    }
                    __typename
                  }
                  putCallDist {
                    calls {
                      size {
                        delimited
                        __typename
                      }
                      sizeRatio
                      premium {
                        contracted
                        __typename
                      }
                      __typename
                    }
                    puts {
                      size {
                        delimited
                        __typename
                      }
                      sizeRatio
                      premium {
                        contracted
                        __typename
                      }
                      __typename
                    }
                    __typename
                  }
                  __typename
                }
                __typename
              }
              lastProcessedItems
              updatedAt
              __typename
            }
            __typename
          }
          __typename
        }
      }
    `;

    this.subscriptionId = `sub_${Date.now()}`;
    const message = {
      id: this.subscriptionId,
      type: GQL_SUBSCRIBE,
      payload: {
        operationName: 'OnOptionsFeedEvents',
        query: subscriptionQuery.trim(),
        variables: {
          id: feedId,
          topic: feedTopic
        },
        extensions: {}
      }
    };
    
    this.send(message);
    console.log('📡 CheddarFlow: Subscription started (ID:', this.subscriptionId + ')');
    console.log('📡 CheddarFlow: Feed ID:', feedId, 'Topic:', feedTopic);
  }

  /**
   * Handle incoming WebSocket messages
   */
  handleMessage(message) {
    switch (message.type) {
      case GQL_CONNECTION_ACK:
        console.log('📡 CheddarFlow: Connection acknowledged');
        this.isConnected = true;
        this.flushMessageQueue();
        this.startSubscription();
        break;

      case GQL_CONNECTION_ERROR:
        console.error('📡 CheddarFlow: Connection error:', message.payload);
        this.isConnected = false;
        if (this.onErrorCallback) {
          this.onErrorCallback(new Error(message.payload?.message || 'Connection error'));
        }
        break;

      case GQL_NEXT:
      case GQL_DATA:
        // Handle both "next" (CheddarFlow's format) and "data" (standard format)
        if (message.id === this.subscriptionId && message.payload?.data) {
          this.handleSubscriptionData(message.payload.data);
        }
        break;

      case GQL_ERROR:
        console.error('📡 CheddarFlow: GraphQL error:', message.payload);
        if (this.onErrorCallback) {
          this.onErrorCallback(new Error(message.payload?.message || 'GraphQL error'));
        }
        break;

      case GQL_COMPLETE:
        console.log('📡 CheddarFlow: Subscription completed');
        this.subscriptionId = null;
        break;

      default:
        // Log unknown types for debugging but don't error
        if (message.type !== 'ping' && message.type !== 'pong') {
          console.log('📡 CheddarFlow: Unknown message type:', message.type);
        }
    }
  }

  /**
   * Handle subscription data from OnOptionsFeedEvents
   * 
   * The data comes in a specific format:
   * - eventData.lastProcessedItems contains a JSON string with tabular data
   * - The JSON has an "ins" array with headers and data rows
   */
  handleSubscriptionData(data) {
    try {
      // CheddarFlow sends data in optionsFeedEvents structure
      const feedEvent = data.optionsFeedEvents;
      if (!feedEvent || !feedEvent.eventData) {
        return;
      }

      const eventData = feedEvent.eventData;
      const eventOperation = feedEvent.eventOperation; // "INSERT", "UPDATE", "DELETE"
      
      // The actual flow data is in lastProcessedItems as a JSON string
      if (!eventData.lastProcessedItems) {
        return;
      }

      let processedItems;
      try {
        processedItems = JSON.parse(eventData.lastProcessedItems);
      } catch (parseError) {
        console.error('📡 CheddarFlow: Error parsing lastProcessedItems:', parseError);
        return;
      }

      // Processed items structure:
      // {
      //   "update": null,
      //   "ins": [
      //     ["idx", "id", "contract_id", "timestamp", "symbol", "expiry", "strike", "put_call", "side", "buy_sell", "spot", "size", "price", "premium", "sweep_block_split", "volume", "open_int", "conds"],
      //     [[0, "id", 123, "timestamp", "QQQ", "12/12/2025", "617", "put", "above", "buy", "616.19", "190", "$2.21", "$42.1K", "sweep", "64,750", "3,681", ["I", "bearish", "unusual"]]]
      //   ]
      // }

      if (!processedItems.ins || !Array.isArray(processedItems.ins) || processedItems.ins.length < 2) {
        return;
      }

      // First array is headers, rest are data rows
      const headers = processedItems.ins[0];
      const dataRows = processedItems.ins.slice(1);

      // Map headers to indices for easier access
      const headerMap = {};
      headers.forEach((header, index) => {
        headerMap[header] = index;
      });

      // Process each data row
      for (const row of dataRows) {
        if (!Array.isArray(row) || row.length === 0) {
          continue;
        }

        // Convert row array to object using headers
        const flowData = {};
        headers.forEach((header, index) => {
          if (row[index] !== undefined) {
            flowData[header] = row[index];
          }
        });

        // Only process INSERT operations for now
        if (eventOperation === 'INSERT' && this.onDataCallback) {
          this.onDataCallback(flowData);
        }
      }
    } catch (error) {
      console.error('📡 CheddarFlow: Error handling subscription data:', error);
    }
  }

  /**
   * Send message to WebSocket
   */
  send(message) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(message));
    } else {
      // Queue message if not connected
      this.messageQueue.push(message);
    }
  }

  /**
   * Flush queued messages
   */
  flushMessageQueue() {
    while (this.messageQueue.length > 0) {
      const message = this.messageQueue.shift();
      this.send(message);
    }
  }

  /**
   * Handle reconnection
   */
  handleReconnect() {
    if (!this.autoReconnect) return;
    
    this.reconnectAttempts++;
    const delay = Math.min(this.reconnectInterval * this.reconnectAttempts, 30000);
    
    console.log(`📡 CheddarFlow: Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
    
    setTimeout(() => {
      this.connect();
    }, delay);
  }

  /**
   * Disconnect from WebSocket
   */
  disconnect() {
    this.autoReconnect = false;
    
    if (this.subscriptionId) {
      this.send({
        id: this.subscriptionId,
        type: 'stop'
      });
    }
    
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
    
    this.isConnected = false;
    console.log('📡 CheddarFlow: Disconnected');
  }
}

/**
 * Map CheddarFlow data to OptionsFlow_monthly schema
 * 
 * CheddarFlow sends data as a row array with these fields (in order):
 * idx, id, contract_id, timestamp, symbol, expiry, strike, put_call, side, buy_sell, 
 * spot, size, price, premium, sweep_block_split, volume, open_int, conds
 */
export function mapCheddarFlowToDatabase(cheddarFlowData) {
  const get = (obj, ...keys) => {
    for (const key of keys) {
      if (obj[key] !== undefined && obj[key] !== null) {
        return String(obj[key]);
      }
    }
    return null;
  };

  const formatDate = (dateStr) => {
    if (!dateStr) return null;
    try {
      const date = new Date(dateStr);
      if (isNaN(date.getTime())) return String(dateStr);
      return date.toISOString().split('T')[0];
    } catch {
      return String(dateStr);
    }
  };

  const formatTime = (timeStr) => {
    if (!timeStr) return null;
    // If it's a timestamp, extract time portion
    if (typeof timeStr === 'string' && timeStr.includes('T')) {
      try {
        const date = new Date(timeStr);
        return date.toTimeString().split(' ')[0];
      } catch {
        return String(timeStr);
      }
    }
    return String(timeStr);
  };

  // Extract timestamp - CheddarFlow uses ISO format
  const timestamp = cheddarFlowData.timestamp || cheddarFlowData.createdAt || cheddarFlowData.time;
  const dateObj = timestamp ? new Date(timestamp) : new Date();
  const createdDate = formatDate(timestamp || dateObj);
  const createdTime = formatTime(timestamp || dateObj);

  // Helper to clean numeric strings (remove $, commas, K, M suffixes)
  const cleanNumeric = (value) => {
    if (!value) return null;
    const str = String(value).replace(/[\s$,]/g, '').toUpperCase();
    if (str.endsWith('K')) {
      return String(parseFloat(str) * 1000);
    } else if (str.endsWith('M')) {
      return String(parseFloat(str) * 1000000);
    }
    return str;
  };

  // Map CheddarFlow field names to database schema
  // CheddarFlow uses: symbol, expiry, strike, put_call, side, buy_sell, spot, size, price, premium, volume, open_int
  return {
    CreatedDate: createdDate,
    CreatedTime: createdTime,
    Symbol: get(cheddarFlowData, 'symbol', 'Symbol', 'ticker', 'Ticker'),
    Type: null, // Not provided by CheddarFlow
    Volume: cleanNumeric(get(cheddarFlowData, 'volume', 'Volume', 'size', 'Size')),
    Price: cleanNumeric(get(cheddarFlowData, 'price', 'Price')),
    Side: get(cheddarFlowData, 'side', 'Side', 'buy_sell', 'buySell'), // "above", "below", "buy", "sell"
    CallPut: get(cheddarFlowData, 'put_call', 'putCall', 'CallPut', 'optionType', 'OptionType')?.toUpperCase() || null,
    Strike: get(cheddarFlowData, 'strike', 'Strike', 'strikePrice', 'StrikePrice'),
    Spot: get(cheddarFlowData, 'spot', 'Spot', 'underlyingPrice', 'UnderlyingPrice', 'stockPrice', 'StockPrice'),
    Premium: cleanNumeric(get(cheddarFlowData, 'premium', 'Premium', 'totalPremium', 'TotalPremium')),
    ExpirationDate: formatDate(get(cheddarFlowData, 'expiry', 'Expiry', 'expirationDate', 'ExpirationDate', 'expiration', 'Expiration')),
    Color: get(cheddarFlowData, 'sweep_block_split', 'sweepBlockSplit', 'Color', 'tradeColor'), // "sweep", "block", "split"
    ImpliedVolatility: null, // Not provided by CheddarFlow in this feed
    Dte: null, // Could calculate from expiry and current date if needed
    ER: null, // Not provided
    StockEtf: 'STOCK', // Default assumption
    Sector: null, // Not provided
    Uoa: null, // Not provided
    Weekly: null, // Could determine from expiry date if needed
    MktCap: null, // Not provided
    OI: cleanNumeric(get(cheddarFlowData, 'open_int', 'openInt', 'OI', 'openInterest', 'OpenInterest'))
  };
}

/**
 * Insert flow record into database
 */
async function insertFlowRecord(record) {
  try {
    // Skip if no symbol
    if (!record.Symbol) {
      return false;
    }

    const insertSQL = `
      INSERT INTO "OptionsFlow_monthly" (
        "CreatedDate", "CreatedTime", "Symbol", "Type", "Volume", "Price",
        "Side", "CallPut", "Strike", "Spot", "Premium", "ExpirationDate",
        "Color", "ImpliedVolatility", "Dte", "ER", "StockEtf", "Sector",
        "Uoa", "Weekly", "MktCap", "OI"
      ) VALUES (
        $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12,
        $13, $14, $15, $16, $17, $18, $19, $20, $21, $22
      )
    `;

    const params = [
      record.CreatedDate,
      record.CreatedTime,
      record.Symbol,
      record.Type,
      record.Volume,
      record.Price,
      record.Side,
      record.CallPut,
      record.Strike,
      record.Spot,
      record.Premium,
      record.ExpirationDate,
      record.Color,
      record.ImpliedVolatility,
      record.Dte,
      record.ER,
      record.StockEtf,
      record.Sector,
      record.Uoa,
      record.Weekly,
      record.MktCap,
      record.OI
    ];

    await rawQuery(insertSQL, params);
    return true;
  } catch (error) {
    console.error('📡 CheddarFlow: Error inserting record:', error);
    return false;
  }
}

// Global client instance
let clientInstance = null;

/**
 * Start CheddarFlow WebSocket service
 */
export function startCheddarFlowService(options = {}) {
  if (clientInstance) {
    console.log('📡 CheddarFlow: Service already running');
    return clientInstance;
  }

  const enableDatabaseInsert = options.enableDatabaseInsert !== false;
  let recordCount = 0;

  clientInstance = new CheddarFlowWebSocketClient({
    url: options.url || process.env.CHEDDARFLOW_WS_URL,
    origin: options.origin || process.env.CHEDDARFLOW_ORIGIN,
    session: options.session || process.env.CHEDDARFLOW_SESSION,
    token: options.token || process.env.CHEDDARFLOW_TOKEN,
    cookies: options.cookies || process.env.CHEDDARFLOW_COOKIES,
    reconnectInterval: options.reconnectInterval,
    maxReconnectAttempts: options.maxReconnectAttempts,
    autoReconnect: options.autoReconnect !== false,
    onData: async (data) => {
      try {
        const mappedRecord = mapCheddarFlowToDatabase(data);
        
        if (enableDatabaseInsert && mappedRecord.Symbol) {
          const inserted = await insertFlowRecord(mappedRecord);
          if (inserted) {
            recordCount++;
            if (recordCount % 10 === 0) {
              console.log(`📡 CheddarFlow: Inserted ${recordCount} records`);
            }
          }
        }

        // Call custom callback if provided
        if (options.onData) {
          options.onData(mappedRecord, data);
        }
      } catch (error) {
        console.error('📡 CheddarFlow: Error processing data:', error);
      }
    },
    onError: (error) => {
      console.error('📡 CheddarFlow: Service error:', error);
      if (options.onError) {
        options.onError(error);
      }
    }
  });

  clientInstance.connect();
  return clientInstance;
}

/**
 * Stop CheddarFlow WebSocket service
 */
export function stopCheddarFlowService() {
  if (clientInstance) {
    clientInstance.disconnect();
    clientInstance = null;
    console.log('📡 CheddarFlow: Service stopped');
  }
}

/**
 * Get service status
 */
export function getCheddarFlowStatus() {
  if (!clientInstance) {
    return { running: false, connected: false };
  }
  
  return {
    running: true,
    connected: clientInstance.isConnected,
    subscriptionId: clientInstance.subscriptionId,
    reconnectAttempts: clientInstance.reconnectAttempts
  };
}

/**
 * Fetch session token from CheddarFlow GraphQL API
 * 
 * Note: This requires authentication (cookies or auth headers from a logged-in session).
 * You can get these from browser DevTools → Application → Cookies after logging into CheddarFlow.
 * 
 * @param {Object} options - Request options
 * @param {string} options.cookie - Cookie string (e.g., "session=xxx; auth=yyy")
 * @param {string} options.authorization - Authorization header (e.g., "Bearer token")
 * @returns {Promise<Object>} Object with session and token
 */
export async function fetchCheddarFlowSession(options = {}) {
  try {
    const fetch = await getFetch();
    
    const query = `
      query GetUserSessionAndMarketStatus {
        marketStatus {
          tradingSession {
            name
            __typename
          }
          lastTradingDay
          __typename
        }
        session {
          token
          roles
          profile {
            appMetadata {
              cancelAt
              subscriptionStatus
              trialEnd
              __typename
            }
            email
            name
            userMetadata {
              timeZone
              optionsAudioAlerts
              __typename
            }
            __typename
          }
          __typename
        }
        user {
          id
          activeOptionsView {
            id
            name
            type
            savedSettings
            __typename
          }
          activeDarkPoolLitView {
            id
            name
            type
            savedSettings
            __typename
          }
          __typename
        }
      }
    `;

    const headers = {
      'Content-Type': 'application/json',
      'Origin': CHEDDARFLOW_ORIGIN,
      'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
    };

    // Add authentication if provided
    if (options.cookie) {
      headers['Cookie'] = options.cookie;
    }
    
    if (options.authorization) {
      headers['Authorization'] = options.authorization;
    }

    const response = await fetch(CHEDDARFLOW_API_URL, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        operationName: 'GetUserSessionAndMarketStatus',
        variables: {},
        query
      })
    });

    if (!response.ok) {
      throw new Error(`CheddarFlow API error: ${response.status} ${response.statusText}`);
    }

    const data = await response.json();
    
    if (data.errors) {
      throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`);
    }

    if (!data.data?.session?.token) {
      throw new Error('No token found in response. Check authentication.');
    }

    return {
      token: data.data.session.token,
      session: options.session || null, // Session ID might be in cookies
      profile: data.data.session.profile,
      marketStatus: data.data.marketStatus,
      user: data.data.user
    };
  } catch (error) {
    console.error('Error fetching CheddarFlow session:', error);
    throw error;
  }
}

export { CheddarFlowWebSocketClient };

