/**
 * TradingView Datafeed implementation using WebSocket backend
 *
 * This adapter translates TradingView's Datafeed API calls into
 * WebSocket messages that match the backend protocol defined in
 * backend/src/datasource/websocket_protocol.py
 *
 * Uses the shared WebSocket connection for all communication.
 */

import { wsManager, type MessageHandler } from './useWebSocket'
import type {
  IBasicDataFeed,
  DatafeedConfiguration,
  LibrarySymbolInfo,
  Bar,
  SearchSymbolResultItem,
  HistoryMetadata
} from '../types/tradingview'

interface PendingRequest {
  resolve: (value: any) => void
  reject: (reason: any) => void
  timeout: number
}

interface Subscription {
  symbolInfo: LibrarySymbolInfo
  resolution: string
  onTick: (bar: Bar) => void
  subscriptionId: string
}

export class WebSocketDatafeed implements IBasicDataFeed {
  private pendingRequests: Map<string, PendingRequest> = new Map()
  private subscriptions: Map<string, Subscription> = new Map()
  private requestTimeout = 10000 // 10 seconds
  private configuration: DatafeedConfiguration | null = null
  private messageHandler: MessageHandler

  constructor() {
    // Use the shared WebSocket connection (managed by App.vue authentication)
    // Register message handler for datafeed messages
    this.messageHandler = this.handleMessage.bind(this)
    wsManager.addHandler(this.messageHandler)
  }

  private generateRequestId(): string {
    return `tv_${Date.now()}_${Math.random().toString(36).substring(7)}`
  }

  private sendRequest<T>(message: any): Promise<T> {
    const requestId = message.request_id || this.generateRequestId()
    message.request_id = requestId

    return new Promise((resolve, reject) => {
      const timeout = window.setTimeout(() => {
        this.pendingRequests.delete(requestId)
        reject(new Error('Request timeout'))
      }, this.requestTimeout)

      this.pendingRequests.set(requestId, { resolve, reject, timeout })
      wsManager.send(message)
    })
  }

  private handleMessage(message: any): void {
    console.log('[TradingView Datafeed] Received message:', message)

    // Handle responses to pending requests
    if (message.request_id && this.pendingRequests.has(message.request_id)) {
      console.log('[TradingView Datafeed] Found pending request for:', message.request_id)
      const pending = this.pendingRequests.get(message.request_id)!
      this.pendingRequests.delete(message.request_id)
      clearTimeout(pending.timeout)

      if (message.type === 'error') {
        console.log('[TradingView Datafeed] Resolving with error:', message.error_message)
        pending.reject(new Error(message.error_message || 'Unknown error'))
      } else {
        console.log('[TradingView Datafeed] Resolving with response')
        pending.resolve(message)
      }
    } else if (message.request_id) {
      console.log('[TradingView Datafeed] No pending request found for:', message.request_id)
    }

    // Handle real-time bar updates
    if (message.type === 'bar_update') {
      const subscription = this.subscriptions.get(message.subscription_id)
      if (subscription && message.bar) {
        const bar: Bar = {
          time: message.bar.time * 1000, // Convert to milliseconds
          open: parseFloat(message.bar.data.open),
          high: parseFloat(message.bar.data.high),
          low: parseFloat(message.bar.data.low),
          close: parseFloat(message.bar.data.close),
          volume: parseFloat(message.bar.data.volume)
        }
        subscription.onTick(bar)
      }
    }
  }

  onReady(callback: (configuration: DatafeedConfiguration) => void): void {
    // TradingView requires callback to be called asynchronously
    if (this.configuration) {
      setTimeout(() => callback(this.configuration!), 0)
      return
    }

    this.sendRequest<any>({
      type: 'get_config'
    })
      .then((response) => {
        this.configuration = response.config
        setTimeout(() => callback(this.configuration!), 0)
      })
      .catch((error) => {
        console.error('Failed to get datafeed configuration:', error)
        // Provide default configuration
        const defaultConfig: DatafeedConfiguration = {
          supported_resolutions: ['1', '5', '15', '30', '60', '240', '1D', '1W'],
          supports_search: true,
          supports_group_request: false,
          supports_marks: false,
          supports_timescale_marks: false,
          supports_time: false
        }
        this.configuration = defaultConfig
        setTimeout(() => callback(defaultConfig), 0)
      })
  }

  searchSymbols(
    userInput: string,
    exchange: string,
    symbolType: string,
    onResult: (items: SearchSymbolResultItem[]) => void
  ): void {
    this.sendRequest<any>({
      type: 'search_symbols',
      query: userInput,
      exchange: exchange || undefined,
      symbol_type: symbolType || undefined,
      limit: 30
    })
      .then((response) => {
        onResult(response.results || [])
      })
      .catch((error) => {
        console.error('Symbol search failed:', error)
        onResult([])
      })
  }

  resolveSymbol(
    symbolName: string,
    onResolve: (symbolInfo: LibrarySymbolInfo) => void,
    onError: (reason: string) => void
  ): void {
    this.sendRequest<any>({
      type: 'resolve_symbol',
      symbol: symbolName
    })
      .then((response) => {
        if (response.symbol_info) {
          console.log('[TradingView Datafeed] Resolved symbol info:', response.symbol_info)
          onResolve(response.symbol_info)
        } else {
          onError('Symbol not found')
        }
      })
      .catch((error) => {
        console.error('Failed to resolve symbol:', error)
        onError(error instanceof Error ? error.message : 'Unknown error')
      })
  }

  getBars(
    symbolInfo: LibrarySymbolInfo,
    resolution: string,
    periodParams: {
      from: number
      to: number
      firstDataRequest: boolean
      countBack?: number
    },
    onResult: (bars: Bar[], meta: HistoryMetadata) => void,
    onError: (reason: string) => void
  ): void {
    this.sendRequest<any>({
      type: 'get_bars',
      symbol: symbolInfo.ticker || symbolInfo.name,
      resolution: resolution,
      from_time: periodParams.from,
      to_time: periodParams.to,
      countback: periodParams.countBack
    })
      .then((response) => {
        if (response.history) {
          const bars: Bar[] = (response.history.bars || []).map((bar: any) => ({
            time: bar.time * 1000, // Convert to milliseconds
            open: parseFloat(bar.data.open),
            high: parseFloat(bar.data.high),
            low: parseFloat(bar.data.low),
            close: parseFloat(bar.data.close),
            volume: parseFloat(bar.data.volume)
          }))

          const meta: HistoryMetadata = {
            noData: bars.length === 0,
            nextTime: response.history.next_time
          }

          onResult(bars, meta)
        } else {
          onResult([], { noData: true })
        }
      })
      .catch((error) => {
        console.error('Failed to get bars:', error)
        onError(error instanceof Error ? error.message : 'Unknown error')
      })
  }

  subscribeBars(
    symbolInfo: LibrarySymbolInfo,
    resolution: string,
    onTick: (bar: Bar) => void,
    listenerGuid: string,
    onResetCacheNeededCallback: () => void
  ): void {
    this.sendRequest<any>({
      type: 'subscribe_bars',
      symbol: symbolInfo.ticker || symbolInfo.name,
      resolution: resolution,
      subscription_id: listenerGuid
    })
      .then((response) => {
        if (response.success) {
          this.subscriptions.set(listenerGuid, {
            symbolInfo,
            resolution,
            onTick,
            subscriptionId: listenerGuid
          })
        }
      })
      .catch((error) => {
        console.error('Failed to subscribe to bars:', error)
      })
  }

  unsubscribeBars(listenerGuid: string): void {
    this.sendRequest<any>({
      type: 'unsubscribe_bars',
      subscription_id: listenerGuid
    })
      .then(() => {
        this.subscriptions.delete(listenerGuid)
      })
      .catch((error) => {
        console.error('Failed to unsubscribe from bars:', error)
      })
  }

  destroy(): void {
    // Clean up all subscriptions
    for (const [guid] of this.subscriptions) {
      this.unsubscribeBars(guid)
    }
    this.subscriptions.clear()

    // Clear pending requests
    for (const [requestId, pending] of this.pendingRequests) {
      clearTimeout(pending.timeout)
      pending.reject(new Error('Datafeed destroyed'))
    }
    this.pendingRequests.clear()

    // Remove message handler from shared WebSocket
    wsManager.removeHandler(this.messageHandler)
  }
}

export function createTradingViewDatafeed(): IBasicDataFeed {
  return new WebSocketDatafeed()
}
