/**
 * SSE Client Service
 *
 * Service for connecting to Server-Sent Events endpoint and receiving
 * real-time receipt status updates. Uses native EventSource API with
 * automatic reconnection.
 *
 * US-031: Frontend - SSE Client Service
 * US-033: Graceful Degradation - Falls back to polling when SSE fails,
 *         retries SSE periodically, and switches back when available.
 *
 * @example
 * import { sseService } from '@data-entry/services/sseService'
 *
 * // Subscribe to status changes
 * sseService.onStatusChange((data) => {
 *   console.log('Status changed:', data.receipt_id, data.status)
 * })
 *
 * // Connect (optionally filter by batch)
 * sseService.connect()
 * sseService.connect('batch-uuid-here')
 *
 * // Disconnect when done
 * sseService.disconnect()
 */

/** @type {EventSource|null} */
let eventSource = null

/** @type {((data: StatusChangeData) => void)|null} */
let statusChangeCallback = null

/** @type {boolean} */
let isConnected = false

// US-033: Graceful Degradation State
/** @type {boolean} - True when SSE is active and working */
let isSSEActive = false

/** @type {boolean} - True when polling fallback is active */
let isPollingActive = false

/** @type {number|null} - Polling interval ID */
let pollingIntervalId = null

/** @type {number|null} - SSE retry timeout ID */
let sseRetryTimeoutId = null

/** @type {(() => Promise<void>)|null} - Polling callback for fetching data */
let pollingCallback = null

/** @type {string|null} - Current batch ID for reconnection */
let currentBatchId = null

/** @type {number} - Number of consecutive SSE failures */
let consecutiveFailures = 0

/** @type {number} - Max failures before giving up SSE retries */
const MAX_CONSECUTIVE_FAILURES = 5

// US-033: Timing constants
const POLLING_INTERVAL_MS = 5000 // 5 seconds between polls
const SSE_RETRY_INTERVAL_MS = 30000 // 30 seconds between SSE retry attempts

/**
 * @typedef {Object} StatusChangeData
 * @property {number} receipt_id - Receipt ID that changed
 * @property {string} status - Workflow status (DRAFT, PENDING_REVIEW, etc.)
 * @property {string|null} processing_status - Processing status (pending, processing, completed, failed)
 * @property {string|null} batch_id - Batch ID this receipt belongs to
 * @property {string} timestamp - ISO timestamp of the event
 */

/**
 * Build SSE endpoint URL with optional batch_id filter.
 *
 * @param {string|null} batchId - Optional batch ID to filter events
 * @returns {string} Full SSE endpoint URL
 */
function buildUrl(batchId) {
  const baseUrl = import.meta.env.BASE_URL + 'api/data-entry/receipts/sse/status'
  if (batchId) {
    return `${baseUrl}?batch_id=${encodeURIComponent(batchId)}`
  }
  return baseUrl
}

/**
 * Connect to SSE endpoint for real-time status updates.
 *
 * If already connected, disconnects first before reconnecting.
 * EventSource handles automatic reconnection on network errors
 * (default retry is 3 seconds, as set by backend).
 *
 * US-033: Enhanced with graceful degradation - falls back to polling on failure.
 *
 * @param {string|null} [batchId=null] - Optional batch ID to filter events for
 */
function connect(batchId = null) {
  // Disconnect existing connection if any
  if (eventSource) {
    closeEventSource()
  }

  // US-033: Store batch ID for reconnection attempts
  currentBatchId = batchId

  const url = buildUrl(batchId)
  console.log(`[SSE] Connecting to ${url}`)

  try {
    eventSource = new EventSource(url)
    isConnected = true

    // Handle incoming messages
    eventSource.onmessage = (event) => {
      try {
        /** @type {StatusChangeData} */
        const data = JSON.parse(event.data)
        console.log('[SSE] Received status change:', data)

        if (statusChangeCallback) {
          statusChangeCallback(data)
        }
      } catch (err) {
        console.error('[SSE] Failed to parse event data:', err, event.data)
      }
    }

    // Handle connection open
    eventSource.onopen = () => {
      console.log('[SSE] Connection opened')
      isConnected = true
      isSSEActive = true

      // US-033: SSE is working, reset failure count
      consecutiveFailures = 0

      // US-033: Stop polling if it was running - SSE is now active
      if (isPollingActive) {
        console.log('[SSE] SSE connection restored, stopping polling fallback')
        stopPolling()
      }

      // US-033: Clear any pending retry timeout
      if (sseRetryTimeoutId) {
        clearTimeout(sseRetryTimeoutId)
        sseRetryTimeoutId = null
      }
    }

    // Handle errors
    eventSource.onerror = (error) => {
      // US-033: Check if connection is permanently closed
      if (eventSource?.readyState === EventSource.CLOSED) {
        console.log('[SSE] Connection closed')
        isConnected = false
        isSSEActive = false

        // US-033: Increment failure count and activate fallback
        consecutiveFailures++
        console.log('SSE connection failed, falling back to polling')
        activateFallbackToPolling()
      } else if (eventSource?.readyState === EventSource.CONNECTING) {
        // EventSource is auto-reconnecting, this is normal behavior
        console.log('[SSE] Connection lost, reconnecting...')
      } else {
        console.error('[SSE] Connection error:', error)
      }
    }
  } catch (err) {
    // US-033: Handle any errors during EventSource creation
    console.error('[SSE] Failed to create EventSource:', err)
    console.log('SSE connection failed, falling back to polling')
    isConnected = false
    isSSEActive = false
    consecutiveFailures++
    activateFallbackToPolling()
  }
}

/**
 * Close the EventSource without full cleanup.
 * Used internally when reconnecting or switching states.
 */
function closeEventSource() {
  if (eventSource) {
    eventSource.close()
    eventSource = null
    isConnected = false
    isSSEActive = false
  }
}

/**
 * US-033: Activate fallback to polling when SSE fails.
 * Also schedules periodic SSE retry attempts.
 */
function activateFallbackToPolling() {
  // Start polling if we have a callback and aren't already polling
  if (pollingCallback && !isPollingActive) {
    startPolling()
  }

  // US-033: Schedule SSE retry if we haven't exceeded max failures
  if (consecutiveFailures < MAX_CONSECUTIVE_FAILURES) {
    scheduleSSERetry()
  } else {
    console.log(`[SSE] Max consecutive failures (${MAX_CONSECUTIVE_FAILURES}) reached, stopping SSE retries`)
  }
}

/**
 * US-033: Start polling as fallback mechanism.
 * Calls the polling callback at regular intervals.
 */
function startPolling() {
  if (isPollingActive || !pollingCallback) {
    return
  }

  console.log(`[SSE] Starting polling fallback (interval: ${POLLING_INTERVAL_MS}ms)`)
  isPollingActive = true

  // Execute immediately, then at intervals
  pollingCallback()

  pollingIntervalId = setInterval(() => {
    if (pollingCallback) {
      pollingCallback()
    }
  }, POLLING_INTERVAL_MS)
}

/**
 * US-033: Stop the polling fallback.
 */
function stopPolling() {
  if (pollingIntervalId) {
    clearInterval(pollingIntervalId)
    pollingIntervalId = null
  }
  isPollingActive = false
  console.log('[SSE] Polling fallback stopped')
}

/**
 * US-033: Schedule a retry attempt to reconnect to SSE.
 * If SSE connects successfully, polling will be stopped automatically.
 */
function scheduleSSERetry() {
  // Don't schedule if already scheduled
  if (sseRetryTimeoutId) {
    return
  }

  console.log(`[SSE] Scheduling SSE retry in ${SSE_RETRY_INTERVAL_MS / 1000}s`)

  sseRetryTimeoutId = setTimeout(() => {
    sseRetryTimeoutId = null

    // Only retry if we're still in fallback mode (polling active)
    if (isPollingActive && !isSSEActive) {
      console.log('[SSE] Retrying SSE connection...')
      connect(currentBatchId)
    }
  }, SSE_RETRY_INTERVAL_MS)
}

/**
 * Disconnect from SSE endpoint.
 *
 * Closes the EventSource connection and cleans up all resources
 * including polling fallback and retry timers.
 *
 * US-033: Enhanced to clean up all graceful degradation state.
 *
 * Safe to call multiple times.
 */
function disconnect() {
  console.log('[SSE] Disconnecting and cleaning up')

  // Close EventSource
  closeEventSource()

  // US-033: Stop polling if active
  stopPolling()

  // US-033: Clear retry timeout
  if (sseRetryTimeoutId) {
    clearTimeout(sseRetryTimeoutId)
    sseRetryTimeoutId = null
  }

  // US-033: Reset state
  currentBatchId = null
  consecutiveFailures = 0
}

/**
 * US-033: Set the polling callback for fallback mechanism.
 *
 * This callback will be called at regular intervals when SSE fails
 * and polling fallback is activated. Should fetch/refresh data.
 *
 * @param {(() => Promise<void>)|null} callback - Async function to call for polling
 */
function setPollingCallback(callback) {
  pollingCallback = callback
}

/**
 * Register callback for status change events.
 *
 * Only one callback can be registered at a time.
 * Call with null to unregister.
 *
 * @param {((data: StatusChangeData) => void)|null} callback - Callback function or null
 */
function onStatusChange(callback) {
  statusChangeCallback = callback
}

/**
 * Check if currently connected to SSE endpoint.
 *
 * @returns {boolean} True if connected
 */
function getIsConnected() {
  return isConnected && eventSource !== null && eventSource.readyState !== EventSource.CLOSED
}

/**
 * US-033: Check if SSE is actively working (not in fallback mode).
 *
 * @returns {boolean} True if SSE is active and receiving events
 */
function getIsSSEActive() {
  return isSSEActive
}

/**
 * US-033: Check if polling fallback is currently active.
 *
 * @returns {boolean} True if polling fallback is running
 */
function getIsPollingActive() {
  return isPollingActive
}

/**
 * SSE Service singleton
 *
 * US-033: Enhanced with graceful degradation capabilities.
 */
export const sseService = {
  connect,
  disconnect,
  onStatusChange,
  setPollingCallback,
  get isConnected() {
    return getIsConnected()
  },
  // US-033: Expose state for debugging/monitoring
  get isSSEActive() {
    return getIsSSEActive()
  },
  get isPollingActive() {
    return getIsPollingActive()
  },
}

// Also export individual functions for flexibility
export { connect, disconnect, onStatusChange, setPollingCallback }
