// form/nc/nc-admin/nc-nats/nc-nats.js
// NATS Testing, Statistics, and Controlling Admin Page

const nats = nc.nats
const change = nc.change
let gridSubscription = null
let changeSubscription = null
let statsUpdateInterval = null
let initialSubscribeFailed = false
let currentBucket = ''
const subscribeTimeoutMs = 500
let natsConnection = null

function time(startTime) {
	return startTime > 0 ? `${((performance.now() - startTime) / 1000).toFixed(3)} sec` : '0.000 sec'
}

onMounted(async () => {
	initCompressionDefaults()
	await initConnection()
	await selectFirstConnection()
	updateStatLabels()
})

onUnmounted(() => {
	if (statsUpdateInterval) {
		clearInterval(statsUpdateInterval)
	}
	if (gridSubscription) {
		console.log(`NATS: '${currentBucket}' unsubscribing grid`)
		const start = performance.now()
		try {
			if (gridSubscription.unsubscribe) {
				gridSubscription.unsubscribe()
			} else if (gridSubscription.close) {
				gridSubscription.close()
			}
			console.log(`NATS: '${currentBucket}' unsubscribed grid ${((performance.now() - start) / 1000).toFixed(3)}s`)
		} catch (err) {
			console.error(`NATS: '${currentBucket}' error unsubscribing grid:`, err)
		}
	}
	if (changeSubscription) {
		console.log(`NATS: '${currentBucket}' unsubscribing change`)
		const start = performance.now()
		try {
			if (changeSubscription.unsubscribe) {
				changeSubscription.unsubscribe()
			}
			console.log(`NATS: '${currentBucket}' unsubscribed change ${((performance.now() - start) / 1000).toFixed(3)}s`)
		} catch (err) {
			console.error(`NATS: '${currentBucket}' error unsubscribing change:`, err)
		}
	}
})

function updateStatLabels() {
	// Message and row count display fields
	rec.received_message_label = `${nc.formatNumber(rec.received_message_total)} messages`
	rec.received_row_label = `${nc.formatNumber(rec.received_row_total)} rows`
	// Rate fields
	rec.progress = rec.total_row_count > 0 ? (rec.grid_row_count / rec.total_row_count) * 100 : null
	rec.message_rate = `${calculateMessageRate()} msgs / sec`
	rec.row_rate = `${calculateRowRate()} rows / sec`
	rec.total_row_count = nats.messageTracker.total_row_count
	const totalTimeMs = rec.end_time > 0 ? rec.end_time - rec.start_time : rec.start_time > 0 ? performance.now() - rec.start_time : 0
	rec.total_time = totalTimeMs > 0 ? `${(totalTimeMs / 1000).toFixed(3)} sec` : ''
	const time = rec.received_message_total > 0 ? (totalTimeMs / rec.received_message_total).toFixed(1) : 0
	rec.avg_time = time > 0 ? `${time} ms / msg` : ''
	// Compression display fields - read from messageTracker
	if (nats.messageTracker.compression_level != null) {
		rec.compression_level = `Level ${nats.messageTracker.compression_level}`
	}
	if (nats.messageTracker.compression_time > 0) {
		rec.compression_time = `${nats.messageTracker.compression_time.toFixed(4)} sec`
	}
	if (nats.messageTracker.decompression_time > 0) {
		rec.decompression_time = `${nats.messageTracker.decompression_time.toFixed(4)} sec`
	}
	if (nats.messageTracker.original_bytes > 0) {
		rec.original_bytes = formatKilobytes(nats.messageTracker.original_bytes)
	}
	if (nats.messageTracker.compressed_bytes > 0) {
		rec.compressed_bytes = formatKilobytes(nats.messageTracker.compressed_bytes)
	}
	if (nats.messageTracker.original_bytes > 0 && nats.messageTracker.compressed_bytes > 0) {
		rec.compression_percent = (((nats.messageTracker.original_bytes - nats.messageTracker.compressed_bytes) / nats.messageTracker.original_bytes) * 100).toFixed(1) + ' %'
	}
	if (rec.progress <= 30) {
		rec.progress_status = 'error'
	} else if (rec.progress <= 90) {
		rec.progress_status = 'warning'
	} else {
		rec.progress_status = 'success'
	}
	// Progress message
	rec.message_text = `${nc.formatNumber(rec.grid_row_count)} / ${nc.formatNumber(rec.total_row_count)} rows (${rec.total_time}, ${nc.formatNumber(rec.received_message_total)} messages, ${rec.message_rate}, ${rec.row_rate})`
}

function initCompressionDefaults() {
	rec.compression_level = 'Off'
	rec.compression_time = ''
	rec.decompression_time = ''
	rec.original_bytes = ''
	rec.compressed_bytes = ''
	rec.compression_percent = ''
}

async function initConnection() {
	const hostname = window.location.hostname
	const isHttps = window.location.protocol === 'https:'
	const testUrlArray = isHttps ? [`wss://${hostname}:4223`, `wss://capacic.com:4223`, `wss://localhost:4223`] : [`ws://${hostname}:4223`, `ws://localhost:4223`, `wss://localhost:4223`]
	for (const url of testUrlArray) {
		try {
			const testConnection = await nats.connectNats({ server: url, test_only: true })
			if (testConnection) {
				const exists = arr.connection.some(c => c.value === url)
				if (!exists) {
					arr.connection.unshift({ value: url, show: url })
					await testConnection.close()
					break
				}
			}
		} catch {
			// Skip failed connections
		}
	}
}

async function selectFirstConnection() {
	if (arr.connection.length > 0 && !rec.connection) {
		rec.connection = arr.connection[0].value
		await connectionChanged()
	}
}

async function connectionChanged() {
	if (!rec.connection) {
		return
	}
	const oldConnection = natsConnection
	natsConnection = null
	rec.connected = false
	rec.connection_info = 'Connecting...'
	updateStatLabels()
	try {
		natsConnection = await nats.connectNats({ server: rec.connection, force: true })
		if (natsConnection) {
			rec.connected = true
			rec.connection_info = natsConnection.getServer()
			updateStatLabels()
		} else {
			rec.connected = false
			rec.connection_info = 'Connection failed'
			updateStatLabels()
		}
	} catch (err) {
		rec.connected = false
		rec.connection_info = 'Connection error: ' + (err?.message || 'unknown')
		updateStatLabels()
		console.error('NATS: connection error:', err)
	}
	if (oldConnection && oldConnection !== natsConnection) {
		try {
			await oldConnection.close()
		} catch {
			// Ignore close errors
		}
	}
}

async function subscribeToNatsBucket(natsConnection, bucket) {
	if (!natsConnection) {
		message.setError(state, 'NATS: cannot subscribe - no connection')
		return
	}
	if (!bucket) {
		message.setError(state, 'NATS: cannot subscribe - no bucket specified')
		return
	}
	console.log(`NATS: '${bucket}' subscribing`)
	const gridStart = performance.now()
	let gridSub = null
	let changeSub = null
	try {
		gridSub = await nats.subscribeToGrid(state, natsConnection, bucket, statMessage)
		const gridTime = ((performance.now() - gridStart) / 1000).toFixed(3)
		const changeStart = performance.now()
		changeSub = await nats.subscribeToChange(state, natsConnection, bucket, changeMessage)
		const changeTime = ((performance.now() - changeStart) / 1000).toFixed(3)
		gridSubscription = gridSub
		changeSubscription = changeSub
		console.log(`NATS: '${bucket}' subscribed [grid: ${gridTime}s, change: ${changeTime}s] ${time(rec.start_time)}`)
	} catch (err) {
		// Cleanup partial subscriptions on failure
		if (gridSub) {
			try {
				if (gridSub.unsubscribe) {
					gridSub.unsubscribe()
				} else if (gridSub.close) {
					gridSub.close()
				}
			} catch (cleanupErr) {
				console.error(`NATS: '${bucket}' cleanup error: ${cleanupErr.message}`)
			}
		}
		gridSubscription = null
		changeSubscription = null
		throw err
	}
}

function statMessage(message, stat) {
	if (!message) {
		return
	}
	// Increment message count (one message received)
	rec.received_message_total += 1
	// Use actual grid data length as source of truth
	rec.grid_row_count = state.grid.message?.data?.length || 0
	rec.received_row_total = rec.grid_row_count
	// Track current test - set message_id on first message
	if (rec.running && !rec.current_message_id) {
		rec.current_message_id = message.message_id
		console.log(`NATS: '${message.bucket}' message_id ${message.message_id} ${time(rec.start_time)}`)
	}
	// Always update stat labels and add stat row
	const statArray = state.grid.message_stat.data
	const statRow = {
		idx: statArray.length + 1,
		message_id: message.message_id,
		msg_index: message.msg_index,
		message_count: message.message_count,
		row_count: stat.row_count,
		bucket: message.bucket,
		is_complete: message.is_complete ? 'Yes' : 'No',
		timestamp: message.timestamp,
		process_time: stat.process_time
	}
	statArray.push(statRow)
	// Update progress and redraw stat grid when stat.redraw is true
	if (rec.running && message.is_complete) {
		const trackerCount = Object.keys(nats.messageTracker.idx).length
		if (trackerCount === 0) {
			console.log(`NATS: '${message.bucket}' complete, rows: ${nc.formatNumber(rec.grid_row_count)} ${time(rec.start_time)}`)
			rec.running = false
			rec.end_time = performance.now()
		}
	}
	if (stat.redraw) {
		updateStatLabels()
		if (stat.redraw_grid || statArray.length <= 100) {
			nc.grid.redrawRows(state, 'message_stat')
		}
	}
	// Capture first message time at end of callback
	if (rec.first_message_time == null) {
		rec.first_message_time = time(rec.start_time)
	}
}

function changeMessage(changeData) {
	if (!changeData) {
		return
	}
	// Skip grid messages (they have data array, not change_arr)
	if (changeData.data) {
		return
	}
	if (!changeData.change_arr) {
		console.log(`NATS: '${currentBucket}' change missing change_arr`)
		return
	}
	console.log(`NATS: '${currentBucket}' change tables:`, Object.keys(changeData.change_arr || {}))
	for (const gridName in changeData.change_arr) {
		const tableArr = changeData.change_arr[gridName]
		console.log(`NATS: '${currentBucket}' table ${gridName} has ${tableArr.length} changes`)
		if (tableArr.length > 0) {
			console.log(`NATS: '${currentBucket}' first change:`, tableArr[0])
		}
	}
	change.incrementalUpdate(state, { change_arr: changeData.change_arr }, 'apply')
	// Refresh all affected grids and set up flash removal
	for (const gridName in changeData.change_arr) {
		nc.grid.refreshCells(state, gridName)
		// Remove flash classes after animation completes (1 second)
		setTimeout(() => {
			nc.grid.refreshCells(state, gridName)
		}, 100)
	}
}

// eslint-disable-next-line no-unused-vars -- Called from layout JSON
function detectChangedRows(currentData, newData) {
	const changed = []
	const currentIdx = {}
	for (const row of currentData) {
		if (row.record_id) {
			currentIdx[row.record_id] = row
		}
	}
	for (const row of newData) {
		if (row.record_id && currentIdx[row.record_id]) {
			const existing = currentIdx[row.record_id]
			const viewed = existing._viewed_modify_id
			if (row.modify_id && row.modify_id !== viewed) {
				row.__changed = true
				changed.push(row.idx)
			}
		}
	}
	return changed
}

// eslint-disable-next-line no-unused-vars -- Called from layout JSON
function calculateBytes(data) {
	let total = 0
	for (const row of data) {
		total += JSON.stringify(row).length
	}
	return total
}

function formatKilobytes(byteCount) {
	const kilobytes = (Number(byteCount) || 0) / 1024
	return `${kilobytes.toFixed(2)} KB`
}

function getTestBucket() {
	return `${rec.bucket}-${rec.row_count}`
}

function calculateMessageRate() {
	const totalTime = (performance.now() - rec.start_time) / 1000
	return totalTime > 0 ? Math.round(rec.received_message_total / totalTime) : 0
}

function calculateRowRate() {
	const totalTime = (performance.now() - rec.start_time) / 1000
	return totalTime > 0 ? Math.round(rec.grid_row_count / totalTime) : 0
}

// eslint-disable-next-line no-unused-vars -- Called from layout JSON
async function startTest() {
	if (rec.running) {
		return
	}
	currentBucket = getTestBucket()
	// Unsubscribe any existing subscription before starting new test
	if (gridSubscription) {
		const bucket = getTestBucket()
		console.log(`NATS: '${bucket}' unsubscribing grid`)
		const start = performance.now()
		try {
			if (gridSubscription.unsubscribe) {
				gridSubscription.unsubscribe()
			} else if (gridSubscription.close) {
				gridSubscription.close()
			}
			console.log(`NATS: '${bucket}' unsubscribed grid ${((performance.now() - start) / 1000).toFixed(3)}s`)
		} catch (err) {
			message.setError(state, `NATS: '${bucket}' error unsubscribing grid: ${err.message}`)
		}
		gridSubscription = null
	}
	if (changeSubscription) {
		const bucket = getTestBucket()
		console.log(`NATS: '${bucket}' unsubscribing change`)
		const start = performance.now()
		try {
			if (changeSubscription.unsubscribe) {
				changeSubscription.unsubscribe()
			}
			console.log(`NATS: '${bucket}' unsubscribed change ${((performance.now() - start) / 1000).toFixed(3)}s`)
		} catch (err) {
			message.setError(state, `NATS: '${bucket}' error unsubscribing change: ${err.message}`)
		}
		changeSubscription = null
	}
	initialSubscribeFailed = false
	rec.current_message_id = ''
	rec.first_message_time = null
	rec.running = true
	rec.start_time = performance.now()
	rec.end_time = 0
	rec.total_row_count = 0
	rec.progress = 0
	rec.received_message_total = 0
	rec.message_rate = 0
	rec.row_rate = 0
	rec.grid_changed_row = ''
	if (state.data) {
		state.data.message = []
	}
	nc.grid.dataChanged(state.grid.message, [])
	nc.grid.dataChanged(state.grid.message_stat, [])
	nats.resetMessageTracker()
	nc.callServer(
		state,
		'nats',
		{
			action: 'send_test',
			rec: {
				bucket: getTestBucket(),
				row_count: rec.row_count,
				stream_prefix: 'GRID_',
				delete_all_prefix_streams: rec.delete_all_prefix_streams,
				delete_old_stream: rec.delete_old_stream
			}
		},
		testResponse
	)
	// Try subscribe immediately - may fail if bucket doesn't exist yet
	const subStart = performance.now()
	const bucket = getTestBucket()
	if (natsConnection) {
		try {
			await nats.withTimeout(subscribeToNatsBucket(natsConnection, bucket), subscribeTimeoutMs, 'subscribe timeout')
		} catch (err) {
			initialSubscribeFailed = true
			console.log(`NATS: '${bucket}' subscribe failed ${((performance.now() - subStart) / 1000).toFixed(3)} sec, error: ${err.message}`)
		}
	} else {
		message.setError(state, `NATS: subscribe '${bucket}' failed - no connection`)
		rec.running = false
	}
	startStatsUpdate()
}

function testResponse(response) {
	if (response.error) {
		message.setError(state, `NATS test error: ${response.error.description || response.error.message || 'Unknown error'}`)
		rec.running = false
		return
	}
	if (response.rec) {
		if (response.rec.complete) {
			// Backend done sending - frontend will handle completion when all messages received
		}
	}
	// Don't retry if subscription was stopped (user clicked stop)
	if (nats.isSubscriptionStopped()) {
		return
	}
	// Retry subscribe if initial attempt failed or subscription not ready yet
	if (initialSubscribeFailed || !gridSubscription) {
		const bucket = getTestBucket()
		console.log(`NATS: '${bucket}' retrying subscribe ${time(rec.start_time)}`)
		const retryStart = performance.now()
		nats.withTimeout(subscribeToNatsBucket(natsConnection, bucket), subscribeTimeoutMs, 'subscribe timeout').catch(err => {
			message.setError(state, `NATS: '${bucket}' retry failed ${((performance.now() - retryStart) / 1000).toFixed(3)} sec, error: ${err?.message || 'unknown'}`)
			rec.running = false
		})
	}
}

// eslint-disable-next-line no-unused-vars -- Called from layout JSON
function stopTest() {
	if (!rec.running) {
		return
	}
	rec.running = false
	rec.end_time = Date.now()
	rec.message_rate = calculateMessageRate()
	stopStatsUpdate()
	// Stop subscription loops
	nats.stopSubscription()
	// Unsubscribe from NATS
	const bucket = currentBucket || getTestBucket()
	if (!gridSubscription && !changeSubscription) {
		console.log(`NATS: '${bucket}' no subscription ${time(rec.start_time)}`)
	}
	if (gridSubscription) {
		console.log(`NATS: '${bucket}' unsubscribing grid ${time(rec.start_time)}`)
		const start = performance.now()
		try {
			if (gridSubscription.unsubscribe) {
				gridSubscription.unsubscribe()
			} else if (gridSubscription.close) {
				gridSubscription.close()
			}
			console.log(`NATS: '${bucket}' unsubscribed grid ${((performance.now() - start) / 1000).toFixed(3)}s`)
		} catch (err) {
			message.setError(state, `NATS: '${bucket}' error unsubscribing grid: ${err.message}`)
		}
		gridSubscription = null
	}
	if (changeSubscription) {
		console.log(`NATS: '${bucket}' unsubscribing change ${time(rec.start_time)}`)
		const start = performance.now()
		try {
			if (changeSubscription.unsubscribe) {
				changeSubscription.unsubscribe()
			}
			console.log(`NATS: '${bucket}' unsubscribed change ${((performance.now() - start) / 1000).toFixed(3)}s`)
		} catch (err) {
			message.setError(state, `NATS: '${bucket}' error unsubscribing change: ${err.message}`)
		}
		changeSubscription = null
	}
}

// eslint-disable-next-line no-unused-vars -- Called from layout JSON
function clearGrid() {
	if (state.data) {
		state.data.message = []
	}
	nc.grid.dataChanged(state.grid.message, [])
	nc.grid.dataChanged(state.grid.message_stat, [])
	rec.grid_row_count = 0
	rec.progress = 0
	rec.total_row_count = 0
	rec.received_message_total = 0
	rec.message_rate = 0
	rec.row_rate = 0
	rec.grid_changed_row = ''
}

function startStatsUpdate() {
	stopStatsUpdate()
	statsUpdateInterval = setInterval(() => {
		if (rec.running) {
			rec.message_rate = calculateMessageRate()
		}
	}, 1000)
}

function stopStatsUpdate() {
	if (statsUpdateInterval) {
		clearInterval(statsUpdateInterval)
		statsUpdateInterval = null
	}
}

// eslint-disable-next-line no-unused-vars -- Called from layout JSON
function highlightChangedRows(gridName, rowIdxArray) {
	for (const idx of rowIdxArray) {
		const row = state.grid[gridName]?.data?.[idx - 1]
		if (row) {
			row.__changed = true
			row.__changed_time = Date.now()
		}
	}
	nc.grid.refreshCells(state, gridName)
	setTimeout(() => {
		for (const idx of rowIdxArray) {
			const row = state.grid[gridName]?.data?.[idx - 1]
			if (row) {
				delete row.__changed_time
			}
		}
		nc.grid.refreshCells(state, gridName)
	}, 1000)
}

// eslint-disable-next-line no-unused-vars -- Called from layout JSON
function gridRowClicked(area, rowIndex, _event, _selected) {
	const row = state.grid[area]?.data?.[rowIndex - 1]
	if (row && row.__changed) {
		row._viewed_modify_id = row.modify_id
		row.__changed = false
		nc.grid.refreshCells(state, area)
	}
}

// eslint-disable-next-line no-unused-vars -- Called from layout JSON
function tabChanged() {
	console.log(`NATS: tab changed: ${rec.tab}`)
}
