import type { TraceEvent } from '../../../types/trace'
import type { HTTPRequest } from '../types/httpRequest'
import { getHostnameFromUrl } from './urlUtils'

export const processHTTPRequests = (traceEvents: TraceEvent[]): HTTPRequest[] => {
  const requestsMap = new Map<string, HTTPRequest>()

  // Process all events and group by requestId
  for (const event of traceEvents) {
    const args = event.args as any
    const requestId = args?.data?.requestId

    if (!requestId) continue

    if (!requestsMap.has(requestId)) {
      requestsMap.set(requestId, {
        requestId,
        url: '',
        hostname: '',
        method: '',
        resourceType: '',
        priority: '',
        events: {},
        timing: { start: event.ts },
        fromCache: false,
        connectionReused: false
      })
    }

    const request = requestsMap.get(requestId)!

    switch (event.name) {
      case 'ResourceWillSendRequest':
        request.events.willSendRequest = event
        request.timing.start = Math.min(request.timing.start, event.ts)
        break

      case 'ResourceSendRequest':
        request.events.sendRequest = event
        request.url = args.data.url || ''
        request.hostname = getHostnameFromUrl(request.url)
        request.method = args.data.requestMethod || ''
        request.resourceType = args.data.resourceType || ''
        request.priority = args.data.priority || ''
        request.timing.start = Math.min(request.timing.start, event.ts)
        
        // Extract connection ID if available
        if (args.data.connectionId !== undefined) {
          request.connectionId = args.data.connectionId
        }
        
        // Extract initiator information
        if (args.data.initiator) {
          const initiator = args.data.initiator
          request.initiator = {
            type: initiator.type || 'other',
            fetchType: initiator.fetchType,
            url: initiator.url,
            lineNumber: initiator.lineNumber,
            columnNumber: initiator.columnNumber,
            stack: initiator.stack
          }
        }
        break

      case 'ResourceReceiveResponse':
        request.events.receiveResponse = event
        request.statusCode = args.data.statusCode
        request.mimeType = args.data.mimeType
        request.protocol = args.data.protocol
        request.responseHeaders = args.data.headers
        
        // Extract connection ID if available (fallback if not set in SendRequest)
        if (args.data.connectionId !== undefined && !request.connectionId) {
          request.connectionId = args.data.connectionId
        }
        
        // Extract content-length from response headers
        if (request.responseHeaders) {
          const contentLengthHeader = request.responseHeaders.find(
            header => header.name.toLowerCase() === 'content-length'
          )
          if (contentLengthHeader) {
            request.contentLength = parseInt(contentLengthHeader.value, 10)
          }
        }
        
        request.fromCache = args.data.fromCache || false
        request.connectionReused = args.data.connectionReused || false
        request.timing.end = event.ts
        
        // Extract network timing details
        const timing = args.data.timing
        if (timing) {
          request.timing.networkDuration = timing.receiveHeadersEnd
          request.timing.dnsStart = timing.dnsStart
          request.timing.dnsEnd = timing.dnsEnd
          request.timing.connectStart = timing.connectStart
          request.timing.connectEnd = timing.connectEnd
          request.timing.sslStart = timing.sslStart
          request.timing.sslEnd = timing.sslEnd
          request.timing.sendStart = timing.sendStart
          request.timing.sendEnd = timing.sendEnd
          request.timing.receiveHeadersStart = timing.receiveHeadersStart
          request.timing.receiveHeadersEnd = timing.receiveHeadersEnd
        }
        break

      case 'ResourceReceivedData':
        if (!request.events.receivedData) request.events.receivedData = []
        request.events.receivedData.push(event)
        request.encodedDataLength = (request.encodedDataLength || 0) + (args.data.encodedDataLength || 0)
        request.timing.end = Math.max(request.timing.end || event.ts, event.ts)
        break

      case 'ResourceFinishLoading':
        request.events.finishLoading = event
        request.timing.end = event.ts
        break
    }

    // Calculate durations and timing
    calculateDurations(request)
    calculateQueueTime(request)
    calculateServerLatency(request)
  }

  return Array.from(requestsMap.values()).filter(req => req.url && req.method)
}

export const calculateDurations = (request: HTTPRequest): void => {
  if (!request.events.receiveResponse) return

  const responseArgs = request.events.receiveResponse.args as any
  const timingData = responseArgs?.data?.timing
  
  // Find the last ResourceReceivedData event timestamp (this matches Chrome DevTools)
  let lastDataTimestamp = 0
  if (request.events.receivedData && request.events.receivedData.length > 0) {
    // Use the last ResourceReceivedData event timestamp
    lastDataTimestamp = request.events.receivedData[request.events.receivedData.length - 1].ts
  } else if (request.events.finishLoading) {
    // Fallback to ResourceFinish timestamp if no data events
    lastDataTimestamp = request.events.finishLoading.ts
  }
  
  // Calculate Download Time (response body transfer time)
  // This is the time from first response byte to last data chunk
  if (timingData?.receiveHeadersStart !== undefined && lastDataTimestamp) {
    const receiveHeadersStartUs = (timingData.requestTime * 1000000) + (timingData.receiveHeadersStart * 1000)
    request.timing.downloadTime = Math.max(0, lastDataTimestamp - receiveHeadersStartUs)
  }
  
  // Note: Duration will be calculated after Total Response Time and Server Latency are available
  
  // Calculate Total Response Time (wall clock time from request initiation to completion)
  // Use the earliest request start time (timing.start) which includes queuing, not sendRequest.ts
  if (request.timing.start && lastDataTimestamp) {
    request.timing.totalResponseTime = lastDataTimestamp - request.timing.start
  } else if (request.events.sendRequest && lastDataTimestamp) {
    // Fallback to sendRequest timestamp if timing.start unavailable
    request.timing.totalResponseTime = lastDataTimestamp - request.events.sendRequest.ts
  } else if (request.timing.queueTime && request.timing.duration) {
    // Fallback: sum all known components
    let totalTime = (request.timing.queueTime || 0) + (request.timing.duration || 0)
    
    // Add DNS time if available
    if (request.timing.dnsStart !== undefined && request.timing.dnsEnd !== undefined && request.timing.dnsStart >= 0) {
      totalTime += (request.timing.dnsEnd - request.timing.dnsStart)
    }
    
    // Add connection time if available
    if (request.timing.connectStart !== undefined && request.timing.connectEnd !== undefined && request.timing.connectStart >= 0) {
      totalTime += (request.timing.connectEnd - request.timing.connectStart)
    }
    
    // Add SSL time if available
    if (request.timing.sslStart !== undefined && request.timing.sslEnd !== undefined && request.timing.sslStart >= 0) {
      totalTime += (request.timing.sslEnd - request.timing.sslStart)
    }
    
    request.timing.totalResponseTime = totalTime
  }
  
  // Calculate Duration as Client-Side Time (Total Response Time - Server Latency)
  // This represents time spent on the client side: queuing, network setup, and download
  if (request.timing.totalResponseTime && request.timing.serverLatency) {
    request.timing.duration = Math.max(0, request.timing.totalResponseTime - request.timing.serverLatency)
  }
  
  // Fallback to trace event timestamps if timing data unavailable
  if (!request.timing.duration && request.timing.end) {
    request.timing.duration = request.timing.end - request.timing.start
  }
}

export const calculateQueueTime = (request: HTTPRequest): void => {
  if (!request.events.receiveResponse) return

  const responseArgs = request.events.receiveResponse.args as any
  const timingData = responseArgs?.data?.timing
  
  if (timingData?.requestTime && timingData?.sendStart !== undefined) {
    // Chrome DevTools queuing calculation methodology:
    // Queuing time = time from request initiation to when network send actually starts
    // This includes DNS resolution, connection establishment, and browser queuing
    
    // requestTime is the baseline timestamp in seconds (when browser initiated the request)
    // sendStart is milliseconds offset from requestTime to when sending began
    const requestTimeUs = timingData.requestTime * 1000000
    const sendStartUs = timingData.sendStart * 1000
    
    // Extract DNS timing
    if (timingData.dnsStart >= 0 && timingData.dnsEnd >= 0) {
      request.timing.dnsStart = timingData.dnsStart * 1000 // Convert to microseconds
      request.timing.dnsEnd = timingData.dnsEnd * 1000
    }
    
    // Extract connection timing
    if (timingData.connectStart >= 0 && timingData.connectEnd >= 0) {
      request.timing.connectStart = timingData.connectStart * 1000 // Convert to microseconds
      request.timing.connectEnd = timingData.connectEnd * 1000
    }
    
    // Extract SSL timing if available
    if (timingData.sslStart >= 0 && timingData.sslEnd >= 0) {
      request.timing.sslStart = timingData.sslStart * 1000 // Convert to microseconds
      request.timing.sslEnd = timingData.sslEnd * 1000
    }
    
    // Extract send/receive timing
    if (timingData.sendStart >= 0) {
      request.timing.sendStart = timingData.sendStart * 1000
    }
    if (timingData.sendEnd >= 0) {
      request.timing.sendEnd = timingData.sendEnd * 1000
    }
    if (timingData.receiveHeadersStart >= 0) {
      request.timing.receiveHeadersStart = timingData.receiveHeadersStart * 1000
    }
    if (timingData.receiveHeadersEnd >= 0) {
      request.timing.receiveHeadersEnd = timingData.receiveHeadersEnd * 1000
    }
    
    // Handle the discrepancy between ResourceSendRequest timestamp and timing.requestTime
    // This accounts for Chrome-internal delays before network timing begins
    if (request.events.sendRequest) {
      const resourceSendRequestTs = request.events.sendRequest.ts
      const chromeInternalDelay = requestTimeUs - resourceSendRequestTs
      
      // If there's a significant delay (>1ms) between trace event and timing baseline
      if (chromeInternalDelay > 1000) {
        // Total queue time = Chrome internal delay + network queue time
        request.timing.queueTime = chromeInternalDelay + sendStartUs
      } else {
        // For more accurate Chrome DevTools matching when no significant delay
        if (timingData.dnsStart >= 0 && timingData.connectStart >= 0) {
          // If we have detailed timing, isolate just the queuing portion
          const dnsStartUs = timingData.dnsStart * 1000
          request.timing.queueTime = dnsStartUs // Pure queuing before DNS
        } else if (timingData.connectStart >= 0) {
          // If no DNS timing but have connection timing
          const connectStartUs = timingData.connectStart * 1000
          request.timing.queueTime = connectStartUs // Queuing before connection
        } else {
          // Default: use sendStart as queue time
          request.timing.queueTime = sendStartUs
        }
      }
    } else {
      // Fallback when no ResourceSendRequest event
      request.timing.queueTime = sendStartUs
    }
    
  } else if (request.events.willSendRequest && request.events.sendRequest) {
    // Legacy fallback: ResourceSendRequest - ResourceWillSendRequest
    request.timing.queueTime = request.events.sendRequest.ts - request.events.willSendRequest.ts
  }
}

export const calculateServerLatency = (request: HTTPRequest): void => {
  if (!request.events.receiveResponse) return

  const responseArgs = request.events.receiveResponse.args as any
  const timingData = responseArgs?.data?.timing
  
  if (timingData) {
    // Chrome DevTools "Waiting (TTFB)" = Time to First Byte
    // This is sendEnd to receiveHeadersStart (time server takes to process and respond)
    if (timingData.receiveHeadersStart !== undefined && timingData.sendEnd !== undefined) {
      // Standard method: sendEnd to receiveHeadersStart (server processing time)
      request.timing.serverLatency = (timingData.receiveHeadersStart - timingData.sendEnd) * 1000 // Convert to microseconds
    } else if (timingData.receiveHeadersStart !== undefined && timingData.sendStart !== undefined) {
      // Fallback: sendStart to receiveHeadersStart (includes send time + server processing)
      request.timing.serverLatency = (timingData.receiveHeadersStart - timingData.sendStart) * 1000
    }
    
    // Ensure non-negative values
    if (request.timing.serverLatency && request.timing.serverLatency < 0) {
      request.timing.serverLatency = 0
    }
  }
}