import { BASE_API } from '@/common/config.js'
import { getToken } from '@/utils/request.js'
import {
	createSseLineBuffer,
	parseConsoleChatSseLine,
	extractAssistantDelta,
	mergeAssistantStreamText
} from '@/utils/sse-parse.js'
import { extractChatCardMeta, mergeChatCardMeta } from '@/utils/draft.js'

function buildUrl(url) {
	if (/^https?:\/\//.test(url)) return url
	const base = (BASE_API || '').replace(/\/$/, '')
	if (!base) return ''
	const path = url.startsWith('/') ? url : `/${url}`
	return `${base}${path}`
}

/**
 * 跨分块 UTF-8 解码，避免中文被切到 chunk 边界时变成 �
 */
function createUtf8StreamDecoder() {
	const native = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8') : null
	let pending = new Uint8Array(0)

	const concat = (a, b) => {
		if (!a || !a.length) return b
		if (!b || !b.length) return a
		const out = new Uint8Array(a.length + b.length)
		out.set(a, 0)
		out.set(b, a.length)
		return out
	}

	/** 从末尾回退不完整的 UTF-8 多字节序列 */
	const splitIncomplete = (bytes) => {
		if (!bytes.length) return { complete: bytes, rest: new Uint8Array(0) }
		let i = bytes.length - 1
		let count = 0
		while (i >= 0 && (bytes[i] & 0xc0) === 0x80) {
			i -= 1
			count += 1
			if (count > 3) break
		}
		if (i < 0) {
			return { complete: new Uint8Array(0), rest: bytes }
		}
		const lead = bytes[i]
		let need = 0
		if ((lead & 0x80) === 0) need = 1
		else if ((lead & 0xe0) === 0xc0) need = 2
		else if ((lead & 0xf0) === 0xe0) need = 3
		else if ((lead & 0xf8) === 0xf0) need = 4
		else need = 1
		const have = bytes.length - i
		if (need > 1 && have < need) {
			return {
				complete: bytes.subarray(0, i),
				rest: bytes.subarray(i)
			}
		}
		return { complete: bytes, rest: new Uint8Array(0) }
	}

	const decodeLatin1Fallback = (bytes) => {
		if (!bytes || !bytes.length) return ''
		let binary = ''
		for (let i = 0; i < bytes.length; i++) {
			binary += String.fromCharCode(bytes[i])
		}
		try {
			return decodeURIComponent(escape(binary))
		} catch (e) {
			return binary
		}
	}

	const toBytes = (data) => {
		if (data instanceof Uint8Array) return data
		if (data instanceof ArrayBuffer) return new Uint8Array(data)
		return null
	}

	return {
		decode(data, stream) {
			if (data == null) return ''
			if (typeof data === 'string') return data
			const chunk = toBytes(data)
			if (!chunk) return ''

			// 优先 TextDecoder（需正确传 stream，结束时再 flush）
			if (native) {
				return native.decode(chunk, { stream: !!stream })
			}

			const merged = concat(pending, chunk)
			if (stream) {
				const parts = splitIncomplete(merged)
				pending = parts.rest
				return decodeLatin1Fallback(parts.complete)
			}
			pending = new Uint8Array(0)
			return decodeLatin1Fallback(merged)
		},
		flush() {
			if (native) {
				try {
					return native.decode()
				} catch (e) {
					return ''
				}
			}
			if (!pending.length) return ''
			const left = pending
			pending = new Uint8Array(0)
			return decodeLatin1Fallback(left)
		}
	}
}

function decodeChunk(data, decoder, stream) {
	if (typeof data === 'string') return data
	if (!(data instanceof ArrayBuffer) && !(data instanceof Uint8Array)) return ''
	if (decoder) {
		return decoder.decode(data, stream)
	}
	const bytes = data instanceof Uint8Array ? data : new Uint8Array(data)
	let binary = ''
	for (let i = 0; i < bytes.length; i++) {
		binary += String.fromCharCode(bytes[i])
	}
	try {
		return decodeURIComponent(escape(binary))
	} catch (e) {
		return binary
	}
}

function getStreamFailMessage(err) {
	const errMsg = (err && err.errMsg) || ''
	if (!errMsg) return '网络连接失败，请稍后重试'
	if (errMsg.includes('abort')) return '请求已取消'
	if (errMsg.includes('timeout')) return '对话超时，请稍后重试'
	if (errMsg.includes('url not in domain list')) {
		return '请求域名未配置，请在开发者工具中关闭域名校验'
	}
	return '网络异常，请稍后重试'
}

function parseFallbackBody(res) {
	if (!res || res.data == null) return ''
	if (typeof res.data === 'string') return res.data
	if (res.data instanceof ArrayBuffer) {
		return decodeChunk(res.data, null, false)
	}
	if (typeof res.data === 'object' && typeof res.data.msg === 'string') {
		throw new Error(res.data.msg)
	}
	return ''
}

/**
 * 微信小程序兼容的 SSE 流式请求（uni.request + enableChunked + onChunkReceived）
 * @returns {() => void} abort
 */
export function streamSseRequest(options = {}) {
	const {
		url,
		data,
		header = {},
		auth = true,
		timeout = 300000,
		onChunk,
		onDone,
		onError
	} = options

	const fullUrl = buildUrl(url)
	if (!fullUrl) {
		const err = new Error('API 地址未配置')
		if (onError) onError(err)
		return () => {}
	}

	const headers = {
		'Content-Type': 'application/json',
		Accept: 'text/event-stream',
		...header
	}

	if (auth) {
		const token = getToken()
		if (token) headers.Authorization = `Bearer ${token}`
	}

	let aborted = false
	let finished = false
	let chunkReceived = false
	let assembledText = ''
	let cardMeta = null
	const utf8Decoder = createUtf8StreamDecoder()
	const lineBuffer = createSseLineBuffer()

	const abort = () => {
		aborted = true
		if (requestTask && typeof requestTask.abort === 'function') {
			requestTask.abort()
		}
	}

	const finish = () => {
		if (finished || aborted) return
		finished = true
		const flushed = utf8Decoder.flush()
		if (flushed) handleText(flushed)
		const lastLine = lineBuffer.flushLine()
		if (lastLine) processLine(lastLine)
		if (onDone) onDone(assembledText, cardMeta)
	}

	const processLine = (line) => {
		if (finished || aborted) return
		const parsed = parseConsoleChatSseLine(line)
		if (!parsed) return
		if (parsed.done) return
		try {
			const meta = extractChatCardMeta(parsed.data)
			if (meta) {
				cardMeta = mergeChatCardMeta(cardMeta, meta)
			}

			const piece = extractAssistantDelta(parsed.data)
			if (!piece) return
			const next = mergeAssistantStreamText(assembledText, piece)
			if (next === assembledText) return
			assembledText = next
			if (onChunk) onChunk(assembledText, parsed.data)
		} catch (e) {
			aborted = true
			if (requestTask && typeof requestTask.abort === 'function') {
				requestTask.abort()
			}
			if (onError) onError(e)
		}
	}

	const handleText = (text) => {
		if (!text || aborted || finished) return
		const lines = lineBuffer.push(text)
		for (const line of lines) {
			processLine(line)
		}
	}

	let requestTask = uni.request({
		url: fullUrl,
		method: 'POST',
		header: headers,
		data,
		enableChunked: true,
		timeout,
		success(res) {
			if (aborted) return

			if (res.statusCode < 200 || res.statusCode >= 300) {
				let message = '对话请求失败'
				try {
					const body = parseFallbackBody(res)
					if (body) {
						const json = JSON.parse(body)
						message = json.msg || json.message || message
					}
				} catch (e) {
					// ignore parse error
				}
				if (onError) onError(new Error(message))
				return
			}

			// 仅在未收到分块时用完整响应兜底，避免 H5/代理把同一份 SSE 再解析一遍导致重复
			if (!chunkReceived) {
				try {
					handleText(parseFallbackBody(res))
				} catch (e) {
					if (onError) onError(e)
					return
				}
			}
			finish()
		},
		fail(err) {
			if (aborted) return
			if (onError) onError(new Error(getStreamFailMessage(err)))
		}
	})

	if (requestTask && typeof requestTask.onChunkReceived === 'function') {
		requestTask.onChunkReceived((res) => {
			if (aborted || finished) return
			chunkReceived = true
			handleText(utf8Decoder.decode(res.data, true))
		})
	}

	return abort
}
