import React, { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react'
import type {
  BroadcastMessage,
  ScanData,
  PauseState,
} from '@/lib/types'

interface ScanManagerContextType {
  activeScan: ScanData | null
  currentScanId: number | null
  isScanning: boolean
  isPaused: boolean
  pauseUntil: number | null
  lastScanCompletedAt: number | null
  lastScanScheduledAt: number | null
  stopScan: (scanId: number) => Promise<void>
  pauseScan: (durationSeconds: number) => Promise<void>
  unpauseScan: () => Promise<void>
  notifyScanScheduled: () => void
}

const ScanManagerContext = createContext<ScanManagerContextType | null>(null)

export function ScanManagerProvider({ children }: { children: React.ReactNode }) {
  const [activeScan, setActiveScan] = useState<ScanData | null>(null)
  const [pauseState, setPauseState] = useState<PauseState | null>(null)
  const [lastScanCompletedAt, setLastScanCompletedAt] = useState<number | null>(null)
  const [lastScanScheduledAt, setLastScanScheduledAt] = useState<number | null>(null)

  const wsRef = useRef<WebSocket | null>(null)
  const reconnectTimerRef = useRef<number | null>(null)
  const lastProcessedState = useRef<{ scan_id: number | null; status: string | null }>({ scan_id: null, status: null })

  // Derived state
  const currentScanId = activeScan?.scan_id ?? null
  const isScanning = activeScan !== null
  const isPaused = pauseState?.paused ?? false
  const pauseUntil = pauseState?.pauseUntil ?? null

  // Process broadcast messages received from backend via WebSocket
  const handleBroadcastMessage = useCallback((message: BroadcastMessage) => {
    // Handle paused message
    if (message.type === 'paused') {
      console.log('[ScanManager] Received paused message:', message.pause_until)
      setPauseState({ paused: true, pauseUntil: message.pause_until })
      setActiveScan(null)  // Clear any active scan when paused
      return
    }

    // Handle no_active_scan message
    if (message.type === 'no_active_scan') {
      setActiveScan(prevScan => {
        // If we already have no active scan, nothing to do
        if (prevScan === null) {
          return null
        }

        // We had a scan but now backend says no active scan
        // This means scan completed while we were disconnected
        console.log('[ScanManager] Received no_active_scan, clearing state and triggering table refresh')
        setLastScanCompletedAt(Date.now())
        lastProcessedState.current = { scan_id: null, status: null }
        return null
      })
      // Clear pause state when going to idle
      setPauseState(null)
      return
    }

    // Handle active_scan message
    const state = message.scan
    const scanId = state.scan_id
    const statusValue = state.status?.status

    // Validate scan ID
    if (!scanId) {
      console.error('[ScanManager] Received active_scan with null scan_id')
      return
    }

    // Deduplicate terminal states only (completed, stopped, error)
    // Running states need all updates for progress tracking
    const isTerminalState = statusValue && ['completed', 'stopped', 'error'].includes(statusValue)
    if (isTerminalState) {
      if (lastProcessedState.current.scan_id === scanId && lastProcessedState.current.status === statusValue) {
        console.log('[ScanManager] Deduplicating terminal state:', { scanId, status: statusValue })
        return
      }
      console.log('[ScanManager] Processing terminal state:', { scanId, status: statusValue })
      lastProcessedState.current = { scan_id: scanId, status: statusValue || null }
    }

    setActiveScan(prevScan => {
      // Check if scan changed (different scan ID)
      const scanChanged = prevScan !== null && prevScan.scan_id !== scanId

      if (scanChanged) {
        console.log('[ScanManager] Scan changed:', { from: prevScan.scan_id, to: scanId })
        // Old scan is implicitly complete, trigger table refresh
        setLastScanCompletedAt(Date.now())
      }

      // Build scan data from message
      const scanData: ScanData = {
        scan_id: scanId,
        root_path: state.root_path,
        phase: 1,
        progress: { current: 0, total: 1, percentage: 0 },
        threads: [],
        completed_phases: state.completed_phases || []
      }

      // Map phase name to phase number
      if (state.current_phase) {
        if (state.current_phase === 'scanning') {
          scanData.phase = 1
        } else if (state.current_phase === 'sweeping') {
          scanData.phase = 2
        } else if (state.current_phase === 'analyzing') {
          scanData.phase = 3
        }
      }

      // Update scanning progress (phase 1)
      if (state.scanning_progress) {
        scanData.scanning_counts = {
          files: state.scanning_progress.files_scanned || 0,
          directories: state.scanning_progress.directories_scanned || 0
        }
      }

      // Update overall progress (phase 3)
      if (state.overall_progress) {
        scanData.progress = {
          current: state.overall_progress.completed,
          total: state.overall_progress.total,
          percentage: state.overall_progress.percentage
        }
      }

      // Update thread states
      if (state.thread_states && state.thread_states.length > 0) {
        scanData.threads = state.thread_states.map(thread => {
          if (thread.operation.type === 'idle') {
            return { thread_index: thread.thread_index, status: 'idle', current_file: null }
          }

          let prefix = ''
          let file = ''
          if (thread.operation.type === 'hashing') {
            prefix = 'Hashing:'
            file = thread.operation.file || ''
          } else if (thread.operation.type === 'validating') {
            prefix = 'Validating:'
            file = thread.operation.file || ''
          }

          return {
            thread_index: thread.thread_index,
            status: 'active' as const,
            current_file: prefix ? `${prefix} ${file}` : file
          }
        })
      }

      // Update scan status
      if (state.status) {
        scanData.status = state.status
        if (state.status.message) {
          scanData.error_message = state.status.message
        }
      }

      // Handle terminal states - trigger table refresh
      if (isTerminalState) {
        console.log('[ScanManager] Terminal state received, triggering table refresh')
        setLastScanCompletedAt(Date.now())
      }

      return scanData
    })
  }, [])

  // Establish persistent WebSocket connection to receive scan state updates
  const connectWebSocket = useCallback(() => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      console.log('WebSocket already connected')
      return
    }

    // Close stale connection if present
    if (wsRef.current) {
      wsRef.current.close()
    }

    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    const wsUrl = `${protocol}//${window.location.host}/ws/scans/progress`

    console.log('Connecting to WebSocket:', wsUrl)
    const ws = new WebSocket(wsUrl)
    wsRef.current = ws

    ws.onopen = () => {
      console.log('WebSocket connected to scan state broadcast')
      if (reconnectTimerRef.current) {
        clearTimeout(reconnectTimerRef.current)
        reconnectTimerRef.current = null
      }
    }

    ws.onmessage = (event) => {
      try {
        const message: BroadcastMessage = JSON.parse(event.data)
        handleBroadcastMessage(message)
      } catch (error) {
        console.error('Failed to parse WebSocket message:', error)
      }
    }

    ws.onerror = (error) => {
      console.error('WebSocket error:', error)
    }

    ws.onclose = (event) => {
      console.log('WebSocket closed:', event.code, event.reason)

      // Automatically reconnect after brief delay
      if (!reconnectTimerRef.current) {
        reconnectTimerRef.current = window.setTimeout(() => {
          reconnectTimerRef.current = null
          console.log('Attempting to reconnect WebSocket...')
          connectWebSocket()
        }, 2000)
      }
    }
  }, [handleBroadcastMessage])

  // Stop scan
  const stopScan = useCallback(async (scanId: number) => {
    try {
      const response = await fetch(`/api/scans/${scanId}/cancel`, {
        method: 'POST'
      })

      if (!response.ok) {
        console.error('Failed to cancel scan:', response.statusText)
        throw new Error('Failed to cancel scan')
      }
    } catch (error) {
      console.error('Error cancelling scan:', error)
      throw error
    }
  }, [])

  // Pause scanning
  const pauseScan = useCallback(async (durationSeconds: number) => {
    try {
      const response = await fetch('/api/pause', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ duration_seconds: durationSeconds })
      })

      if (!response.ok) {
        console.error('Failed to pause:', response.statusText)
        throw new Error('Failed to pause')
      }
    } catch (error) {
      console.error('Error pausing:', error)
      throw error
    }
  }, [])

  // Unpause scanning
  const unpauseScan = useCallback(async () => {
    try {
      const response = await fetch('/api/pause', {
        method: 'DELETE'
      })

      if (!response.ok) {
        const text = await response.text()
        console.error('Failed to unpause:', text)
        throw new Error(text || 'Failed to unpause')
      }
    } catch (error) {
      console.error('Error unpausing:', error)
      throw error
    }
  }, [])

  // Notify that a scan was scheduled (triggers refresh in UpcomingScansTable)
  const notifyScanScheduled = useCallback(() => {
    setLastScanScheduledAt(Date.now())
  }, [])

  // Initialize WebSocket connection on mount
  useEffect(() => {
    connectWebSocket()

    return () => {
      if (wsRef.current) {
        wsRef.current.close()
      }
      if (reconnectTimerRef.current) {
        clearTimeout(reconnectTimerRef.current)
      }
    }
  }, [connectWebSocket])

  const value: ScanManagerContextType = {
    activeScan,
    currentScanId,
    isScanning,
    isPaused,
    pauseUntil,
    lastScanCompletedAt,
    lastScanScheduledAt,
    stopScan,
    pauseScan,
    unpauseScan,
    notifyScanScheduled,
  }

  return (
    <ScanManagerContext.Provider value={value}>
      {children}
    </ScanManagerContext.Provider>
  )
}

export function useScanManager() {
  const context = useContext(ScanManagerContext)
  if (!context) {
    throw new Error('useScanManager must be used within ScanManagerProvider')
  }
  return context
}
