import type {
  DebugEmlErrorCode,
  DebugEmlErrorResponse,
  DebugEmlStageEvent,
  DebugEmlStreamHandlers,
  DebugEmlSuperAgentRunResult,
  DebugEmlSuperAgentTraceEvent,
  DebugEmlUploadInput,
} from '@/types/debugEml'
import { getStoredAccessToken } from '@/services/authSession'
import { isAuthSessionInvalidPayload, notifyUnauthorized } from '@/services/httpClient'

const apiBaseUrl = import.meta.env.VITE_API_BASE_URL ?? ''
const debugEmlEndpoint = '/api/system/debug/eml-superagent-runs'
const debugEmlStreamEndpoint = `${debugEmlEndpoint}/stream`
const jsonSecretPattern =
  /("(?:api[_-]?key|token|secret|password|cookie|authorization)"\s*:\s*")[^"]*(")/gi
const headerSecretPattern = /((?:authorization|cookie)\s*:\s*)[^\s,;]+/gi
const textSecretPattern = /((?:api[_-]?key|token|secret|password)\s*[=:]\s*)[^\s,;}]+/gi

export class DebugEmlUploadError extends Error {
  readonly status: number
  readonly errorCode: DebugEmlErrorCode
  readonly details: unknown

  constructor(message: string, status: number, errorCode: DebugEmlErrorCode, details: unknown) {
    super(message)
    this.name = 'DebugEmlUploadError'
    this.status = status
    this.errorCode = errorCode
    this.details = details
  }
}

export async function uploadDebugEmlSuperAgentRun(
  input: DebugEmlUploadInput,
): Promise<DebugEmlSuperAgentRunResult> {
  const form = new FormData()
  form.append('file', input.file)
  const hotelId = input.hotelId?.trim()
  if (hotelId) {
    form.append('hotel_id', hotelId)
  }
  const runLabel = input.runLabel?.trim()
  if (runLabel) {
    form.append('run_label', runLabel)
  }

  const response = await fetch(`${apiBaseUrl}${debugEmlEndpoint}`, {
    method: 'POST',
    headers: buildUploadHeaders(input.debugUploadKey),
    body: form,
  })

  const payload = await readResponsePayload(response)
  if (!response.ok) {
    if (isAuthSessionInvalidPayload(payload)) {
      notifyUnauthorized()
    }
    const errorPayload = isDebugEmlErrorResponse(payload) ? payload : {}
    throw new DebugEmlUploadError(
      errorPayload.message || `Request failed with status ${response.status}`,
      response.status,
      errorPayload.error_code || 'DEBUG_EML_RUN_FAILED',
      payload,
    )
  }

  return normalizeDebugEmlResult(payload)
}

export async function uploadDebugEmlSuperAgentRunStream(
  input: DebugEmlUploadInput,
  handlers: DebugEmlStreamHandlers = {},
): Promise<DebugEmlSuperAgentRunResult> {
  const response = await fetch(`${apiBaseUrl}${debugEmlStreamEndpoint}`, {
    method: 'POST',
    headers: buildUploadHeaders(input.debugUploadKey, 'text/event-stream'),
    body: buildUploadForm(input),
  })

  if (!response.ok) {
    const payload = await readResponsePayload(response)
    if (isAuthSessionInvalidPayload(payload)) {
      notifyUnauthorized()
    }
    const errorPayload = isDebugEmlErrorResponse(payload) ? payload : {}
    throw new DebugEmlUploadError(
      errorPayload.message || `Request failed with status ${response.status}`,
      response.status,
      errorPayload.error_code || 'DEBUG_EML_RUN_FAILED',
      payload,
    )
  }

  if (!response.body) {
    throw new DebugEmlUploadError('Debug EML SSE 响应为空。', response.status, 'DEBUG_EML_RUN_FAILED', null)
  }

  let finalResult: DebugEmlSuperAgentRunResult | null = null
  let streamError: DebugEmlUploadError | null = null
  await readSseStream(response.body, (event) => {
    const payload = parseSsePayload(event.data)
    if (event.event === 'debug_stage') {
      handlers.onStage?.(normalizeStageEvent(payload))
      return
    }
    if (event.event === 'superagent_trace') {
      handlers.onTrace?.(normalizeTraceEvent(payload))
      return
    }
    if (event.event === 'debug_heartbeat') {
      return
    }
    if (event.event === 'superagent_result') {
      finalResult = normalizeDebugEmlResult(payload)
      handlers.onResult?.(finalResult)
      return
    }
    if (event.event === 'debug_error') {
      const errorPayload = isDebugEmlErrorResponse(payload) ? payload : {}
      streamError = new DebugEmlUploadError(
        errorPayload.message || 'Debug EML 调试失败。',
        response.status,
        errorPayload.error_code || 'DEBUG_EML_RUN_FAILED',
        payload,
      )
    }
  })

  if (streamError) {
    throw streamError
  }
  if (!finalResult) {
    throw new DebugEmlUploadError('Debug EML SSE 未收到最终结果。', response.status, 'DEBUG_EML_RUN_FAILED', null)
  }
  return finalResult
}

function buildUploadForm(input: DebugEmlUploadInput): FormData {
  const form = new FormData()
  form.append('file', input.file)
  const hotelId = input.hotelId?.trim()
  if (hotelId) {
    form.append('hotel_id', hotelId)
  }
  const runLabel = input.runLabel?.trim()
  if (runLabel) {
    form.append('run_label', runLabel)
  }
  return form
}

function buildUploadHeaders(
  debugUploadKey: string,
  accept: 'application/json' | 'text/event-stream' = 'application/json',
): Record<string, string> {
  const headers: Record<string, string> = {
    Accept: accept,
    'X-TH-Hotel-Debug-Upload-Key': debugUploadKey.trim(),
  }
  const token = getStoredAccessToken()
  if (token) {
    headers.Authorization = `Bearer ${token}`
  }
  return headers
}

async function readResponsePayload(response: Response): Promise<unknown> {
  const contentType = response.headers.get('content-type') ?? ''
  if (!contentType.includes('application/json')) {
    return response.text()
  }
  return response.json()
}

function normalizeDebugEmlResult(payload: unknown): DebugEmlSuperAgentRunResult {
  const result = isRecord(payload) ? payload : {}
  return {
    debug_run_id: stringValue(result.debug_run_id),
    source_message_id: nullableStringValue(result.source_message_id),
    source_provider: nullableStringValue(result.source_provider),
    external_message_id: nullableStringValue(result.external_message_id),
    external_conversation_id: nullableStringValue(result.external_conversation_id),
    original_eml_oss_url: nullableStringValue(result.original_eml_oss_url),
    original_eml_sha256: nullableStringValue(result.original_eml_sha256),
    uploaded_media: Array.isArray(result.uploaded_media)
      ? result.uploaded_media.map(normalizeUploadedMedia)
      : [],
    html_body_with_oss_urls: nullableStringValue(result.html_body_with_oss_urls),
    html_body_sanitized: nullableStringValue(result.html_body_sanitized),
    html_sanitize_required:
      typeof result.html_sanitize_required === 'boolean' ? result.html_sanitize_required : null,
    html_render_mode: nullableStringValue(result.html_render_mode),
    agentbus_like_payload: isRecord(result.agentbus_like_payload) ? result.agentbus_like_payload : null,
    superagent_session_id: nullableStringValue(result.superagent_session_id),
    superagent_run_id: nullableStringValue(result.superagent_run_id),
    superagent_raw_answer: nullableStringValue(result.superagent_raw_answer),
    superagent_parsed_json: result.superagent_parsed_json ?? null,
    superagent_trace_events: Array.isArray(result.superagent_trace_events)
      ? result.superagent_trace_events.map(normalizeTraceEvent)
      : [],
    warnings: Array.isArray(result.warnings) ? result.warnings.filter(isString) : [],
    status: nullableStringValue(result.status),
  }
}

async function readSseStream(
  body: ReadableStream<Uint8Array>,
  onEvent: (event: { event: string; data: string }) => void,
): Promise<void> {
  const reader = body.getReader()
  const decoder = new TextDecoder()
  let buffer = ''
  while (true) {
    const { value, done } = await reader.read()
    if (done) {
      break
    }
    buffer += decoder.decode(value, { stream: true })
    buffer = consumeSseBuffer(buffer, onEvent)
  }
  buffer += decoder.decode()
  consumeSseBuffer(`${buffer}\n\n`, onEvent)
}

function consumeSseBuffer(
  buffer: string,
  onEvent: (event: { event: string; data: string }) => void,
): string {
  let remaining = buffer
  while (true) {
    const separatorIndex = remaining.search(/\r?\n\r?\n/)
    if (separatorIndex < 0) {
      return remaining
    }
    const block = remaining.slice(0, separatorIndex)
    remaining = remaining.slice(separatorIndex + (remaining[separatorIndex] === '\r' ? 4 : 2))
    const event = parseSseBlock(block)
    if (event) {
      onEvent(event)
    }
  }
}

function parseSseBlock(block: string): { event: string; data: string } | null {
  let event = 'message'
  const dataLines: string[] = []
  for (const line of block.split(/\r?\n/)) {
    if (line.startsWith('event:')) {
      event = line.slice('event:'.length).trim()
      continue
    }
    if (line.startsWith('data:')) {
      dataLines.push(line.slice('data:'.length).trim())
    }
  }
  if (!dataLines.length && event !== 'done') {
    return null
  }
  return {
    event,
    data: dataLines.join('\n'),
  }
}

function parseSsePayload(data: string): unknown {
  if (!data.trim()) {
    return {}
  }
  try {
    return JSON.parse(data)
  } catch {
    return data
  }
}

function normalizeStageEvent(value: unknown): DebugEmlStageEvent {
  const stage = isRecord(value) ? value : {}
  return {
    debug_run_id: nullableStringValue(stage.debug_run_id),
    status: nullableStringValue(stage.status),
    safe_summary: nullableStringValue(stage.safe_summary),
  }
}

function normalizeTraceEvent(value: unknown): DebugEmlSuperAgentTraceEvent {
  const trace = isRecord(value) ? value : {}
  return {
    event: nullableMaskedStringValue(trace.event),
    run_id: nullableMaskedStringValue(trace.run_id),
    message_id: nullableMaskedStringValue(trace.message_id),
    tool_call_id: nullableMaskedStringValue(trace.tool_call_id),
    tool_name: nullableMaskedStringValue(trace.tool_name),
    text: nullableMaskedStringValue(trace.text),
    input_summary: nullableMaskedStringValue(trace.input_summary),
    output_summary: nullableMaskedStringValue(trace.output_summary),
    status: nullableMaskedStringValue(trace.status),
    ts: nullableMaskedStringValue(trace.ts),
  }
}

function normalizeUploadedMedia(value: unknown): DebugEmlSuperAgentRunResult['uploaded_media'][number] {
  const media = isRecord(value) ? value : {}
  return {
    media_type: nullableStringValue(media.media_type),
    file_name: nullableStringValue(media.file_name),
    content_type: nullableStringValue(media.content_type),
    size_bytes: typeof media.size_bytes === 'number' ? media.size_bytes : null,
    external_url: nullableStringValue(media.external_url),
    external_media_id: nullableStringValue(media.external_media_id),
    object_key: nullableStringValue(media.object_key),
  }
}

function isDebugEmlErrorResponse(value: unknown): value is DebugEmlErrorResponse {
  return isRecord(value) && (typeof value.error_code === 'string' || typeof value.message === 'string')
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

function isString(value: unknown): value is string {
  return typeof value === 'string'
}

function nullableStringValue(value: unknown): string | null {
  return typeof value === 'string' ? value : null
}

function stringValue(value: unknown): string {
  return typeof value === 'string' ? value : ''
}

function nullableMaskedStringValue(value: unknown): string | null {
  if (typeof value !== 'string') {
    return null
  }
  return value
    .replace(jsonSecretPattern, '$1***$2')
    .replace(headerSecretPattern, '$1***')
    .replace(textSecretPattern, '$1***')
}
