/*
 * Copyright (C) 2026 SHX VPN
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package org.strongswan.android.ui.compose

import android.app.Application
import android.app.Service
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.ServiceConnection
import android.os.IBinder
import android.os.SystemClock
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.viewModelScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.strongswan.android.data.VpnProfile
import org.strongswan.android.data.VpnProfileSource
import org.strongswan.android.logic.CharonVpnService
import org.strongswan.android.logic.VpnStateService
import org.strongswan.android.logic.VpnStateService.ErrorState
import org.strongswan.android.logic.VpnStateService.State
import org.strongswan.android.ui.VpnProfileControlActivity
import org.strongswan.android.utils.Constants

data class TrafficUi(
	val bytesIn: Long = 0,
	val bytesOut: Long = 0,
	val rateIn: Long = 0,
	val rateOut: Long = 0,
	val durationSec: Long = 0,
)

enum class ShxTab
{
	HOME,
	PROFILES,
	SETTINGS,
}

data class HomeUiState(
	val vpnState: State = State.DISABLED,
	val error: ErrorState = ErrorState.NO_ERROR,
	val errorText: Int = 0,
	val activeProfile: VpnProfile? = null,
	val profiles: List<VpnProfile> = emptyList(),
	val selected: VpnProfile? = null,
	val detailsOpen: Boolean = false,
	val tab: ShxTab = ShxTab.HOME,
)

class HomeViewModel(app: Application) : AndroidViewModel(app), VpnStateService.VpnStateListener
{
	private val _ui = MutableStateFlow(HomeUiState())
	val ui: StateFlow<HomeUiState> = _ui

	/* kept separate from HomeUiState so traffic ticks don't recompose the whole app */
	private val _traffic = MutableStateFlow(TrafficUi())
	val traffic: StateFlow<TrafficUi> = _traffic

	private var service: VpnStateService? = null

	/* written from the main thread (pullState) and the poll coroutine (Default) */
	@Volatile private var lastIn = 0L
	@Volatile private var lastOut = 0L
	@Volatile private var lastAt = 0L
	@Volatile private var connectedAt = 0L

	private val connection = object : ServiceConnection
	{
		override fun onServiceConnected(name: ComponentName?, binder: IBinder?)
		{
			service = (binder as VpnStateService.LocalBinder).service
			service?.registerListener(this@HomeViewModel)
			pullState()
		}

		override fun onServiceDisconnected(name: ComponentName?)
		{
			service = null
		}
	}

	private val profilesChanged = object : BroadcastReceiver()
	{
		override fun onReceive(context: Context?, intent: Intent?)
		{
			reloadProfiles()
		}
	}

	init
	{
		val ctx = getApplication<Application>()
		ctx.bindService(Intent(ctx, VpnStateService::class.java), connection, Service.BIND_AUTO_CREATE)
		LocalBroadcastManager.getInstance(ctx).registerReceiver(
			profilesChanged,
			IntentFilter(Constants.VPN_PROFILES_CHANGED)
		)
		reloadProfiles()
		/* poll traffic only while the app is visible, and off the main thread */
		viewModelScope.launch {
			ProcessLifecycleOwner.get().lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
				while (isActive)
				{
					tickTraffic()
					delay(TRAFFIC_POLL_MS)
				}
			}
		}
	}

	override fun onCleared()
	{
		val ctx = getApplication<Application>()
		service?.unregisterListener(this)
		try
		{
			ctx.unbindService(connection)
		}
		catch (_: IllegalArgumentException)
		{
		}
		LocalBroadcastManager.getInstance(ctx).unregisterReceiver(profilesChanged)
		super.onCleared()
	}

	override fun stateChanged()
	{
		pullState()
	}

	fun setTab(tab: ShxTab)
	{
		_ui.update { it.copy(tab = tab) }
	}

	fun select(profile: VpnProfile)
	{
		_ui.update { it.copy(selected = profile) }
	}

	fun openDetails(open: Boolean)
	{
		_ui.update { it.copy(detailsOpen = open) }
	}

	fun connect(context: Context, profile: VpnProfile)
	{
		val intent = Intent(context, VpnProfileControlActivity::class.java)
		intent.action = VpnProfileControlActivity.START_PROFILE
		intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_UUID, profile.getUUID().toString())
		context.startActivity(intent)
	}

	fun disconnect()
	{
		service?.disconnect()
	}

	fun reconnect()
	{
		service?.reconnect()
	}

	fun delete(profile: VpnProfile)
	{
		viewModelScope.launch(Dispatchers.IO) {
			val source = VpnProfileSource(getApplication())
			source.open()
			source.deleteVpnProfile(profile)
			source.close()
			val intent = Intent(Constants.VPN_PROFILES_CHANGED)
			intent.putExtra(Constants.VPN_PROFILES_MULTIPLE, arrayOf(profile.getUUID().toString()))
			LocalBroadcastManager.getInstance(getApplication()).sendBroadcast(intent)
			reloadProfiles()
		}
	}

	private fun reloadProfiles()
	{
		viewModelScope.launch(Dispatchers.IO) {
			val source = VpnProfileSource(getApplication())
			source.open()
			val all = source.allVpnProfiles.sortedBy { it.name?.lowercase() ?: "" }
			source.close()
			_ui.update { state ->
				val selected = all.firstOrNull { it.getUUID() == state.selected?.getUUID() }
					?: all.firstOrNull { it.getUUID() == state.activeProfile?.getUUID() }
					?: all.firstOrNull()
				state.copy(profiles = all, selected = selected)
			}
		}
	}

	private fun pullState()
	{
		val svc = service ?: return
		val state = svc.state
		if (state == State.CONNECTED && connectedAt == 0L)
		{
			connectedAt = SystemClock.elapsedRealtime()
			lastIn = 0
			lastOut = 0
			lastAt = 0
		}
		if (state != State.CONNECTED)
		{
			connectedAt = 0
			_traffic.value = TrafficUi()
		}
		_ui.update {
			it.copy(
				vpnState = state,
				error = svc.errorState,
				errorText = svc.errorText,
				activeProfile = svc.profile,
				selected = svc.profile ?: it.selected,
			)
		}
	}

	private suspend fun tickTraffic()
	{
		if (_ui.value.vpnState != State.CONNECTED)
		{
			return
		}
		val now = SystemClock.elapsedRealtime()
		val stats = try
		{
			withContext(Dispatchers.Default) { CharonVpnService.queryTraffic() }
		}
		catch (_: Throwable)
		{
			null
		} ?: longArrayOf(0, 0)
		val inbound = stats.getOrElse(0) { 0L }
		val outbound = stats.getOrElse(1) { 0L }
		val dt = if (lastAt == 0L) TRAFFIC_POLL_MS / 1000.0 else ((now - lastAt) / 1000.0).coerceAtLeast(0.2)
		val rateIn = if (lastAt == 0L) 0 else ((inbound - lastIn) / dt).toLong().coerceAtLeast(0)
		val rateOut = if (lastAt == 0L) 0 else ((outbound - lastOut) / dt).toLong().coerceAtLeast(0)
		val startedAt = connectedAt
		lastIn = inbound
		lastOut = outbound
		lastAt = now
		if (startedAt == 0L)
		{
			return
		}
		_traffic.value = TrafficUi(
			bytesIn = inbound,
			bytesOut = outbound,
			rateIn = rateIn,
			rateOut = rateOut,
			durationSec = (now - startedAt) / 1000,
		)
	}

	companion object
	{
		private const val TRAFFIC_POLL_MS = 500L
	}
}
