package com.durian.tssparty.presentation.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.durian.tssparty.data.repository.TssRepository
import com.durian.tssparty.data.repository.TssRepository.JoinKeygenViaGrpcResult
import com.durian.tssparty.domain.model.*
import com.durian.tssparty.util.AddressUtils
import com.durian.tssparty.util.TransactionUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: TssRepository
) : ViewModel() {

    // App State (similar to Zustand store)
    private val _appState = MutableStateFlow(AppState())
    val appState: StateFlow<AppState> = _appState.asStateFlow()

    // UI State
    private val _uiState = MutableStateFlow(MainUiState())
    val uiState: StateFlow<MainUiState> = _uiState.asStateFlow()

    // Settings
    private val _settings = MutableStateFlow(AppSettings())
    val settings: StateFlow<AppSettings> = _settings.asStateFlow()

    // Share records
    val shares: StateFlow<List<ShareRecord>> = repository.getAllShares()
        .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

    // Session status from repository
    val sessionStatus: StateFlow<SessionStatus> = repository.sessionStatus

    // Created session invite code
    private val _createdInviteCode = MutableStateFlow<String?>(null)
    val createdInviteCode: StateFlow<String?> = _createdInviteCode.asStateFlow()

    // Flag to track if user has entered the session (clicked "进入会话")
    private val _hasEnteredSession = MutableStateFlow(false)
    val hasEnteredSession: StateFlow<Boolean> = _hasEnteredSession.asStateFlow()

    // Synchronous flag to prevent participant_joined from adding duplicates after session_started
    // This is set immediately (synchronously) when session_started is processed, ensuring
    // any subsequent participant_joined events in the same callback queue will see the flag
    private var sessionStartedForSession: String? = null

    init {
        // Start initialization on app launch
        checkAllServices()

        // Setup session event callback (like Electron does after connection)
        setupSessionEventCallback()
    }

    /**
     * Check all services for startup
     */
    fun checkAllServices() {
        viewModelScope.launch {
            _appState.update { it.copy(appReady = AppReadyState.INITIALIZING) }

            var hasError = false
            var errorMessage: String? = null

            // 1. Check Database
            try {
                val walletCount = repository.getShareCount()
                _appState.update {
                    it.copy(
                        walletCount = walletCount,
                        environment = it.environment.copy(
                            database = ServiceStatus(
                                isOnline = true,
                                message = "正常"
                            )
                        )
                    )
                }
            } catch (e: Exception) {
                hasError = true
                errorMessage = "数据库错误: ${e.message}"
                _appState.update {
                    it.copy(
                        environment = it.environment.copy(
                            database = ServiceStatus(
                                isOnline = false,
                                message = e.message ?: "错误"
                            )
                        )
                    )
                }
            }

            // 2. Check Message Router
            try {
                val serverUrl = _settings.value.messageRouterUrl
                val parts = serverUrl.split(":")
                val host = parts[0]
                val port = parts.getOrNull(1)?.toIntOrNull() ?: 50051

                repository.connect(host, port)
                val partyId = repository.registerParty()

                _appState.update {
                    it.copy(
                        partyId = partyId,
                        environment = it.environment.copy(
                            messageRouter = ServiceStatus(
                                isOnline = true,
                                message = "已连接"
                            )
                        )
                    )
                }
                _uiState.update { it.copy(isConnected = true) }
            } catch (e: Exception) {
                hasError = true
                if (errorMessage == null) errorMessage = "消息路由: ${e.message}"
                _appState.update {
                    it.copy(
                        environment = it.environment.copy(
                            messageRouter = ServiceStatus(
                                isOnline = false,
                                message = e.message ?: "连接失败"
                            )
                        )
                    )
                }
                _uiState.update { it.copy(isConnected = false) }
            }

            // 3. Check Kava API
            try {
                val startTime = System.currentTimeMillis()
                val isHealthy = repository.checkKavaHealth()
                val latency = System.currentTimeMillis() - startTime

                _appState.update {
                    it.copy(
                        environment = it.environment.copy(
                            kavaApi = ServiceStatus(
                                isOnline = isHealthy,
                                message = if (isHealthy) "正常" else "不可用",
                                latency = latency
                            )
                        )
                    )
                }
            } catch (e: Exception) {
                // Kava API is not critical
                _appState.update {
                    it.copy(
                        environment = it.environment.copy(
                            kavaApi = ServiceStatus(
                                isOnline = false,
                                message = e.message ?: "错误"
                            )
                        )
                    )
                }
            }

            // Small delay for visual feedback
            delay(1000)

            // Update final state
            _appState.update {
                it.copy(
                    appReady = if (hasError) AppReadyState.ERROR else AppReadyState.READY,
                    appError = errorMessage
                )
            }
        }
    }

    /**
     * Connect to Message Router server
     */
    fun connectToServer(serverUrl: String) {
        viewModelScope.launch {
            try {
                val parts = serverUrl.split(":")
                val host = parts[0]
                val port = parts.getOrNull(1)?.toIntOrNull() ?: 50051

                repository.connect(host, port)
                repository.registerParty()

                _uiState.update { it.copy(isConnected = true, error = null) }
                _appState.update {
                    it.copy(
                        environment = it.environment.copy(
                            messageRouter = ServiceStatus(isOnline = true, message = "已连接")
                        )
                    )
                }
            } catch (e: Exception) {
                _uiState.update { it.copy(isConnected = false, error = e.message) }
                _appState.update {
                    it.copy(
                        environment = it.environment.copy(
                            messageRouter = ServiceStatus(isOnline = false, message = e.message ?: "失败")
                        )
                    )
                }
            }
        }
    }

    // Current session data for CreateWalletScreen
    private val _currentSessionId = MutableStateFlow<String?>(null)
    val currentSessionId: StateFlow<String?> = _currentSessionId.asStateFlow()

    private val _sessionParticipants = MutableStateFlow<List<String>>(emptyList())
    val sessionParticipants: StateFlow<List<String>> = _sessionParticipants.asStateFlow()

    private val _currentRound = MutableStateFlow(0)
    val currentRound: StateFlow<Int> = _currentRound.asStateFlow()

    private val _totalRounds = MutableStateFlow(0)
    val totalRounds: StateFlow<Int> = _totalRounds.asStateFlow()

    private val _publicKey = MutableStateFlow<String?>(null)
    val publicKey: StateFlow<String?> = _publicKey.asStateFlow()

    /**
     * Create a new keygen session (initiator)
     * Matches Electron behavior: creates session via API, then auto-joins via gRPC
     */
    fun createKeygenSession(walletName: String, thresholdT: Int, thresholdN: Int, participantName: String) {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }

            val result = repository.createKeygenSession(walletName, thresholdT, thresholdN, participantName)

            result.fold(
                onSuccess = { sessionResult ->
                    _createdInviteCode.value = sessionResult.inviteCode
                    _currentSessionId.value = sessionResult.sessionId
                    // Store party index for later use
                    _currentRound.value = 0

                    android.util.Log.d("MainViewModel", "Keygen session created: sessionId=${sessionResult.sessionId}, partyIndex=${sessionResult.partyIndex}")

                    // Fetch current session status to get all participants (including server-party-co-managed)
                    // This matches Electron's behavior of calling getSessionStatus after session creation
                    val statusResult = repository.getSessionStatus(sessionResult.sessionId)
                    statusResult.fold(
                        onSuccess = { status ->
                            val participantNames = status.participants.mapIndexed { index, p ->
                                if (p.partyId == repository.getPartyId()) {
                                    participantName  // Use the name provided by user for self
                                } else {
                                    "参与方 ${index + 1}"  // Generic name for others
                                }
                            }
                            _sessionParticipants.value = participantNames
                            android.util.Log.d("MainViewModel", "Session status fetched: ${status.participants.size} participants already joined")
                            android.util.Log.d("MainViewModel", "  Participants: ${status.participants.map { it.partyId }}")
                        },
                        onFailure = { e ->
                            // Fallback to just self if status fetch fails
                            _sessionParticipants.value = listOf(participantName)
                            android.util.Log.w("MainViewModel", "Failed to fetch session status: ${e.message}, using self only")
                        }
                    )

                    _uiState.update { it.copy(isLoading = false) }
                },
                onFailure = { e ->
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    // Store initiator keygen info
    private var initiatorKeygenInfo: InitiatorKeygenInfo? = null

    /**
     * Setup session event callback (called during initialization)
     * This mirrors Electron's behavior of subscribing to events after connection
     * Handles events for:
     * - Initiator (CreateWallet)
     * - Joiner (JoinKeygen)
     * - CoSign joiner (参与签名)
     */
    private fun setupSessionEventCallback() {
        android.util.Log.d("MainViewModel", "Setting up session event callback")

        // Setup keygen timeout callback (matching Electron's 5-minute timeout in checkAndTriggerKeygen)
        repository.setKeygenTimeoutCallback { errorMessage ->
            android.util.Log.e("MainViewModel", "[IDLE_CRASH_DEBUG] Keygen timeout callback invoked: $errorMessage")
            try {
                _uiState.update { it.copy(isLoading = false, error = errorMessage, countdownSeconds = -1L) }
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG] Keygen timeout callback completed")
            } catch (e: Exception) {
                android.util.Log.e("MainViewModel", "[IDLE_CRASH_DEBUG] Exception in keygen timeout callback: ${e.message}")
                android.util.Log.e("MainViewModel", "[IDLE_CRASH_DEBUG] Stack: ${e.stackTraceToString()}")
            }
        }

        // Setup countdown tick callback for UI countdown display
        repository.setCountdownTickCallback { remainingSeconds ->
            try {
                _uiState.update { it.copy(countdownSeconds = remainingSeconds) }
            } catch (e: Exception) {
                android.util.Log.e("MainViewModel", "[IDLE_CRASH_DEBUG] Exception in countdown tick callback: ${e.message}")
            }
        }

        // Setup progress callback for real-time round updates from native TSS bridge
        repository.setProgressCallback { round, totalRoundsFromGo ->
            android.util.Log.d("MainViewModel", "Progress update: $round / $totalRoundsFromGo")
            // Update totalRounds from Go library (keygen=4, sign=9)
            _totalRounds.value = totalRoundsFromGo
            // Update the appropriate round state based on which session type is active
            when {
                // Initiator keygen (CreateWallet)
                _currentSessionId.value != null && pendingJoinKeygenInfo == null && pendingJoinSignInfo == null -> {
                    _currentRound.value = round
                }
                // Joiner keygen (JoinKeygen)
                pendingJoinKeygenInfo != null -> {
                    _joinKeygenRound.value = round
                }
                // Joiner sign (CoSign/参与签名)
                pendingJoinSignInfo != null -> {
                    _coSignRound.value = round
                }
                // Initiator sign (Transfer)
                _signSessionId.value != null -> {
                    _signCurrentRound.value = round
                }
            }
        }

        repository.setSessionEventCallback { event ->
            try {
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG] === MainViewModel received session event ===")
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG]   eventType: ${event.eventType}")
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG]   sessionId: ${event.sessionId}")
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG]   _currentSessionId: ${_currentSessionId.value}")
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG]   pendingJoinKeygenInfo?.sessionId: ${pendingJoinKeygenInfo?.sessionId}")
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG]   pendingJoinSignInfo?.sessionId: ${pendingJoinSignInfo?.sessionId}")
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG]   _signSessionId: ${_signSessionId.value}")
                android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG]   pendingSignInitiatorInfo?.sessionId: ${pendingSignInitiatorInfo?.sessionId}")

            when (event.eventType) {
                "session_started" -> {
                    // CRITICAL: Set flag immediately (synchronously) to prevent subsequent
                    // participant_joined events from adding duplicates. This must be the
                    // first line before any async operations.
                    sessionStartedForSession = event.sessionId
                    android.util.Log.d("MainViewModel", "[IDLE_CRASH_DEBUG] Session started flag set for: ${event.sessionId}")

                    // Check if this is for keygen initiator (CreateWallet)
                    val currentSessionId = _currentSessionId.value
                    if (currentSessionId != null && event.sessionId == currentSessionId) {
                        android.util.Log.d("MainViewModel", "Session started event for keygen initiator, triggering keygen")
                        // Ensure participant list has exactly N parties (fill if incomplete, don't add more)
                        if (_sessionParticipants.value.size < event.thresholdN) {
                            _sessionParticipants.value = (1..event.thresholdN).map { "参与方 $it" }
                        }
                        viewModelScope.launch {
                            startKeygenAsInitiator(
                                sessionId = currentSessionId,
                                thresholdT = event.thresholdT,
                                thresholdN = event.thresholdN,
                                selectedParties = event.selectedParties
                            )
                        }
                    }

                    // Check if this is for keygen joiner (JoinKeygen)
                    val joinKeygenInfo = pendingJoinKeygenInfo
                    if (joinKeygenInfo != null && event.sessionId == joinKeygenInfo.sessionId) {
                        android.util.Log.d("MainViewModel", "Session started event for keygen joiner, triggering keygen")
                        // Ensure participant list has exactly N parties
                        if (_joinKeygenParticipants.value.size < event.thresholdN) {
                            _joinKeygenParticipants.value = (1..event.thresholdN).map { "参与方 $it" }
                        }
                        startKeygenAsJoiner()
                    }

                    // Check if this is for sign joiner (CoSign/参与签名)
                    val joinSignInfo = pendingJoinSignInfo
                    if (joinSignInfo != null && event.sessionId == joinSignInfo.sessionId) {
                        android.util.Log.d("MainViewModel", "Session started event for sign joiner, triggering sign")
                        // Ensure participant list has exactly T parties
                        if (_coSignParticipants.value.size < event.thresholdT) {
                            _coSignParticipants.value = (1..event.thresholdT).map { "参与方 $it" }
                        }
                        startSignAsJoiner()
                    }

                    // Check if this is for sign initiator (TransferScreen - 发起签名)
                    val signSessionId = _signSessionId.value
                    android.util.Log.d("MainViewModel", "Checking for sign initiator: signSessionId=$signSessionId, eventSessionId=${event.sessionId}")
                    if (signSessionId != null && event.sessionId == signSessionId) {
                        android.util.Log.d("MainViewModel", "Session started event for sign initiator, triggering sign")
                        // Ensure participant list has exactly T parties
                        if (_signParticipants.value.size < event.thresholdT) {
                            _signParticipants.value = (1..event.thresholdT).map { "参与方 $it" }
                        }
                        startSignAsInitiator(event.selectedParties)
                    } else {
                        android.util.Log.d("MainViewModel", "NOT triggering sign initiator: signSessionId=$signSessionId, pendingSignInitiatorInfo=${pendingSignInitiatorInfo?.sessionId}")
                    }
                }
                "party_joined", "participant_joined" -> {
                    android.util.Log.d("MainViewModel", "Processing participant_joined event...")

                    // CRITICAL: Check synchronous flag first - if session_started was already
                    // processed for this session, don't add more participants
                    // This is 100% reliable because the flag is set synchronously in session_started
                    // handler before any async operations, and callbacks are processed sequentially
                    if (sessionStartedForSession == event.sessionId) {
                        android.util.Log.d("MainViewModel", "  Session already started for ${event.sessionId}, ignoring participant_joined")
                        return@setSessionEventCallback
                    }

                    // Update participant count for initiator's CreateWallet screen
                    val currentSessionId = _currentSessionId.value
                    android.util.Log.d("MainViewModel", "  Checking for initiator: currentSessionId=$currentSessionId, eventSessionId=${event.sessionId}")
                    if (currentSessionId != null && event.sessionId == currentSessionId) {
                        android.util.Log.d("MainViewModel", "  → Matched initiator session! Updating _sessionParticipants")
                        _sessionParticipants.update { current ->
                            val newParticipant = "参与方 ${current.size + 1}"
                            android.util.Log.d("MainViewModel", "  → Adding participant: $newParticipant, total now: ${current.size + 1}")
                            current + newParticipant
                        }
                    }

                    // Update participant count for keygen joiner's JoinKeygen screen
                    val joinKeygenInfo = pendingJoinKeygenInfo
                    android.util.Log.d("MainViewModel", "  Checking for joiner: joinKeygenInfo?.sessionId=${joinKeygenInfo?.sessionId}")
                    if (joinKeygenInfo != null && event.sessionId == joinKeygenInfo.sessionId) {
                        android.util.Log.d("MainViewModel", "  → Matched joiner session! Updating _joinKeygenParticipants")
                        _joinKeygenParticipants.update { current ->
                            val newParticipant = "参与方 ${current.size + 1}"
                            current + newParticipant
                        }
                    }

                    // Update participant count for sign joiner's CoSign screen
                    val joinSignInfo = pendingJoinSignInfo
                    if (joinSignInfo != null && event.sessionId == joinSignInfo.sessionId) {
                        android.util.Log.d("MainViewModel", "  → Matched sign joiner session! Updating _coSignParticipants")
                        _coSignParticipants.update { current ->
                            val newParticipant = "参与方 ${current.size + 1}"
                            current + newParticipant
                        }
                    }

                    // Update participant count for sign initiator's TransferScreen (SigningScreen)
                    val signSessionId = _signSessionId.value
                    android.util.Log.d("MainViewModel", "  Checking for sign initiator: signSessionId=$signSessionId, eventSessionId=${event.sessionId}")
                    if (signSessionId != null && event.sessionId == signSessionId) {
                        android.util.Log.d("MainViewModel", "  → Matched sign initiator session! Updating _signParticipants")
                        _signParticipants.update { current ->
                            val newParticipant = "参与方 ${current.size + 1}"
                            android.util.Log.d("MainViewModel", "  → Adding participant: $newParticipant, total now: ${current.size + 1}")
                            current + newParticipant
                        }
                    }
                }
                "all_joined" -> {
                    android.util.Log.d("MainViewModel", "All parties joined, starting session status polling as fallback")

                    // Start polling as fallback mechanism (matching Electron's checkAndTriggerKeygen)
                    // This handles the case where gRPC stream disconnects (e.g., app goes to background)
                    // and session_started event is missed
                    val currentSessionId = _currentSessionId.value
                    val joinKeygenInfo = pendingJoinKeygenInfo
                    val joinSignInfo = pendingJoinSignInfo
                    val signSessionId = _signSessionId.value

                    when {
                        // Initiator keygen session
                        currentSessionId != null && event.sessionId == currentSessionId -> {
                            android.util.Log.d("MainViewModel", "Starting polling for initiator keygen session: $currentSessionId")
                            repository.startSessionStatusPolling(currentSessionId, "keygen")
                        }
                        // Joiner keygen session
                        joinKeygenInfo != null && event.sessionId == joinKeygenInfo.sessionId -> {
                            android.util.Log.d("MainViewModel", "Starting polling for joiner keygen session: ${joinKeygenInfo.sessionId}")
                            repository.startSessionStatusPolling(joinKeygenInfo.sessionId, "keygen")
                        }
                        // Sign joiner session
                        joinSignInfo != null && event.sessionId == joinSignInfo.sessionId -> {
                            android.util.Log.d("MainViewModel", "Starting polling for sign joiner session: ${joinSignInfo.sessionId}")
                            repository.startSessionStatusPolling(joinSignInfo.sessionId, "sign")
                        }
                        // Sign initiator session
                        signSessionId != null && event.sessionId == signSessionId -> {
                            android.util.Log.d("MainViewModel", "Starting polling for sign initiator session: $signSessionId")
                            repository.startSessionStatusPolling(signSessionId, "sign")
                        }
                    }
                }
            }
            } catch (e: Exception) {
                android.util.Log.e("MainViewModel", "[IDLE_CRASH_DEBUG] Exception in session event callback!")
                android.util.Log.e("MainViewModel", "[IDLE_CRASH_DEBUG] Event: ${event.eventType}, sessionId: ${event.sessionId}")
                android.util.Log.e("MainViewModel", "[IDLE_CRASH_DEBUG] Exception: ${e.javaClass.simpleName}: ${e.message}")
                android.util.Log.e("MainViewModel", "[IDLE_CRASH_DEBUG] Stack: ${e.stackTraceToString()}")
            }
        }
    }

    /**
     * Enter session (transition from created to session screen)
     * The session event subscription is already active from initialization
     */
    fun enterSession() {
        // Session events are already being listened to via the callback set in init
        // This transitions the UI to the session view
        val sessionId = _currentSessionId.value
        android.util.Log.d("MainViewModel", "Entering session: $sessionId")
        _hasEnteredSession.value = true
    }

    /**
     * Start keygen process as initiator
     */
    private suspend fun startKeygenAsInitiator(
        sessionId: String,
        thresholdT: Int,
        thresholdN: Int,
        selectedParties: List<String>
    ) {
        android.util.Log.d("MainViewModel", "Starting keygen as initiator: sessionId=$sessionId, t=$thresholdT, n=$thresholdN")

        val result = repository.startKeygenAsInitiator(
            sessionId = sessionId,
            thresholdT = thresholdT,
            thresholdN = thresholdN,
            password = "" // No password needed, same as Electron
        )

        result.fold(
            onSuccess = { share ->
                _publicKey.value = share.publicKey
                _uiState.update {
                    it.copy(
                        lastCreatedAddress = share.address,
                        successMessage = "钱包创建成功！"
                    )
                }
                // Update wallet count
                _appState.update { state ->
                    state.copy(walletCount = state.walletCount + 1)
                }
            },
            onFailure = { e ->
                _uiState.update { it.copy(error = e.message) }
            }
        )
    }

    /**
     * Reset session state (back to home)
     */
    fun resetSessionState() {
        _currentSessionId.value = null
        _sessionParticipants.value = emptyList()
        _currentRound.value = 0
        _totalRounds.value = 0
        _publicKey.value = null
        _createdInviteCode.value = null
        _hasEnteredSession.value = false
        // Reset synchronous flag for fresh session
        sessionStartedForSession = null
        // Reset session status to WAITING for fresh start
        repository.resetSessionStatus()
    }

    // ========== Join Keygen State ==========

    private val _joinSessionInfo = MutableStateFlow<JoinKeygenSessionInfo?>(null)
    val joinSessionInfo: StateFlow<JoinKeygenSessionInfo?> = _joinSessionInfo.asStateFlow()

    private val _joinKeygenParticipants = MutableStateFlow<List<String>>(emptyList())
    val joinKeygenParticipants: StateFlow<List<String>> = _joinKeygenParticipants.asStateFlow()

    private val _joinKeygenRound = MutableStateFlow(0)
    val joinKeygenRound: StateFlow<Int> = _joinKeygenRound.asStateFlow()

    private val _joinKeygenPublicKey = MutableStateFlow<String?>(null)
    val joinKeygenPublicKey: StateFlow<String?> = _joinKeygenPublicKey.asStateFlow()

    // Store invite code, joinToken and password for auto-join (matching Electron)
    private var pendingInviteCode: String = ""
    private var pendingJoinToken: String = ""
    private var pendingPassword: String = ""

    /**
     * Validate invite code and get session info
     * Matches Electron's grpc:validateInviteCode - returns sessionInfo + joinToken
     */
    fun validateInviteCode(inviteCode: String) {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }
            pendingInviteCode = inviteCode

            val result = repository.validateInviteCode(inviteCode)

            result.fold(
                onSuccess = { validateResult ->
                    val info = validateResult.sessionInfo
                    // Store joinToken for later use (matching Electron)
                    pendingJoinToken = validateResult.joinToken

                    _joinSessionInfo.value = JoinKeygenSessionInfo(
                        sessionId = info.sessionId,
                        walletName = info.walletName,
                        thresholdT = info.thresholdT,
                        thresholdN = info.thresholdN,
                        initiator = info.initiator,
                        currentParticipants = info.currentParticipants,
                        totalParticipants = info.totalParticipants
                    )
                    _uiState.update { it.copy(isLoading = false) }

                    android.util.Log.d("MainViewModel", "Validate success: sessionId=${info.sessionId}, joinToken length=${pendingJoinToken.length}")
                },
                onFailure = { e ->
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    /**
     * Join a keygen session (called after validateInviteCode succeeds)
     * Matches Electron's grpc:joinSession flow:
     * 1. Uses stored sessionInfo and joinToken from validateInviteCode
     * 2. Joins via gRPC
     * 3. Waits for session_started event to trigger keygen
     */
    fun joinKeygen(inviteCode: String, password: String) {
        viewModelScope.launch {
            val sessionInfo = _joinSessionInfo.value
            if (sessionInfo == null) {
                _uiState.update { it.copy(error = "会话信息不完整") }
                return@launch
            }

            if (pendingJoinToken.isEmpty()) {
                _uiState.update { it.copy(error = "未获取到加入令牌，请重新验证邀请码") }
                return@launch
            }

            _uiState.update { it.copy(isLoading = true, error = null) }
            pendingPassword = password

            android.util.Log.d("MainViewModel", "Joining keygen session: sessionId=${sessionInfo.sessionId}, joinToken length=${pendingJoinToken.length}")

            // Join session via gRPC (matching Electron's grpc:joinSession)
            val result = repository.joinKeygenSessionViaGrpc(
                sessionId = sessionInfo.sessionId,
                joinToken = pendingJoinToken,
                walletName = sessionInfo.walletName,
                thresholdT = sessionInfo.thresholdT,
                thresholdN = sessionInfo.thresholdN
            )

            result.fold(
                onSuccess = { joinResult ->
                    android.util.Log.d("MainViewModel", "gRPC join success: partyIndex=${joinResult.partyIndex}, sessionStatus=${joinResult.sessionStatus}")

                    // Store join info for when session_started event arrives
                    pendingJoinKeygenInfo = PendingJoinKeygenInfo(
                        sessionId = sessionInfo.sessionId,
                        partyIndex = joinResult.partyIndex,
                        thresholdT = sessionInfo.thresholdT,
                        thresholdN = sessionInfo.thresholdN,
                        walletName = sessionInfo.walletName,
                        password = password
                    )

                    _uiState.update { it.copy(isLoading = false) }

                    // Match Electron behavior exactly:
                    // If session is already in_progress, trigger keygen immediately
                    // Otherwise wait for session_started event from gRPC subscription
                    android.util.Log.d("MainViewModel", "Join result sessionStatus: ${joinResult.sessionStatus}")
                    if (joinResult.sessionStatus == "in_progress") {
                        android.util.Log.d("MainViewModel", "Session already in_progress, triggering keygen immediately (Solution B)")
                        startKeygenAsJoiner()
                    } else {
                        android.util.Log.d("MainViewModel", "Joined session ${sessionInfo.sessionId}, waiting for session_started event")
                    }
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "gRPC join failed", e)
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    // Store pending join keygen info for when session_started event arrives
    private var pendingJoinKeygenInfo: PendingJoinKeygenInfo? = null

    /**
     * Start keygen as joiner (called when session_started event is received)
     */
    private fun startKeygenAsJoiner() {
        val joinInfo = pendingJoinKeygenInfo ?: return

        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }

            // Initialize participant list with all N parties (keygen requires all parties)
            // This ensures UI shows correct participant count even if we missed some participant_joined events
            _joinKeygenParticipants.value = (1..joinInfo.thresholdN).map { "参与方 $it" }

            android.util.Log.d("MainViewModel", "Starting keygen as joiner: sessionId=${joinInfo.sessionId}, partyIndex=${joinInfo.partyIndex}, thresholdN=${joinInfo.thresholdN}")

            val result = repository.executeKeygenAsJoiner(
                sessionId = joinInfo.sessionId,
                partyIndex = joinInfo.partyIndex,
                thresholdT = joinInfo.thresholdT,
                thresholdN = joinInfo.thresholdN,
                password = joinInfo.password
            )

            result.fold(
                onSuccess = { share ->
                    _joinKeygenPublicKey.value = share.publicKey
                    _uiState.update {
                        it.copy(
                            isLoading = false,
                            lastCreatedAddress = share.address,
                            successMessage = "钱包创建成功！"
                        )
                    }
                    // Update wallet count
                    _appState.update { state ->
                        state.copy(walletCount = state.walletCount + 1)
                    }
                    // Clear pending info
                    pendingJoinKeygenInfo = null
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "Keygen execution failed", e)
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    /**
     * Reset join keygen state
     */
    fun resetJoinKeygenState() {
        _joinSessionInfo.value = null
        _joinKeygenParticipants.value = emptyList()
        _joinKeygenRound.value = 0
        _joinKeygenPublicKey.value = null
        pendingInviteCode = ""
        pendingJoinToken = ""
        pendingPassword = ""
        pendingJoinKeygenInfo = null
        // Reset synchronous flag for fresh session
        sessionStartedForSession = null
        // Reset session status to WAITING for fresh start
        repository.resetSessionStatus()
    }

    // ========== CoSign (Join Sign) State ==========

    private val _coSignSessionInfo = MutableStateFlow<CoSignSessionInfo?>(null)
    val coSignSessionInfo: StateFlow<CoSignSessionInfo?> = _coSignSessionInfo.asStateFlow()

    private val _coSignParticipants = MutableStateFlow<List<String>>(emptyList())
    val coSignParticipants: StateFlow<List<String>> = _coSignParticipants.asStateFlow()

    private val _coSignRound = MutableStateFlow(0)
    val coSignRound: StateFlow<Int> = _coSignRound.asStateFlow()

    private val _coSignSignature = MutableStateFlow<String?>(null)
    val coSignSignature: StateFlow<String?> = _coSignSignature.asStateFlow()

    // Store pending CoSign data (matching Electron's activeCoSignSession)
    private var pendingCoSignInviteCode: String = ""
    private var pendingCoSignJoinToken: String = ""
    private var pendingJoinSignInfo: PendingJoinSignInfo? = null

    /**
     * Validate sign session invite code
     * Matches Electron's cosign:validateInviteCode - returns sessionInfo + joinToken + parties
     */
    fun validateSignInviteCode(inviteCode: String) {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }
            pendingCoSignInviteCode = inviteCode

            val result = repository.validateSignInviteCode(inviteCode)

            result.fold(
                onSuccess = { validateResult ->
                    val info = validateResult.signSessionInfo
                    // Store joinToken for later use (matching Electron)
                    pendingCoSignJoinToken = validateResult.joinToken

                    _coSignSessionInfo.value = CoSignSessionInfo(
                        sessionId = info.sessionId,
                        keygenSessionId = info.keygenSessionId,
                        walletName = info.walletName,
                        messageHash = info.messageHash,
                        thresholdT = info.thresholdT,
                        thresholdN = info.thresholdN,
                        currentParticipants = info.currentParticipants,
                        parties = info.parties  // Include complete parties list
                    )
                    _uiState.update { it.copy(isLoading = false) }

                    android.util.Log.d("MainViewModel", "Validate sign success: sessionId=${info.sessionId}, parties=${info.parties.size}, joinToken length=${pendingCoSignJoinToken.length}")
                },
                onFailure = { e ->
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    /**
     * Join a sign session (called after validateSignInviteCode succeeds)
     * Matches Electron's cosign:joinSession flow:
     * 1. Uses stored sessionInfo and joinToken from validateSignInviteCode
     * 2. Joins via gRPC
     * 3. If session is already in_progress, trigger sign immediately
     * 4. Otherwise waits for session_started event to trigger sign
     */
    fun joinSign(inviteCode: String, shareId: Long, password: String) {
        viewModelScope.launch {
            val sessionInfo = _coSignSessionInfo.value
            if (sessionInfo == null) {
                _uiState.update { it.copy(error = "会话信息不完整") }
                return@launch
            }

            if (pendingCoSignJoinToken.isEmpty()) {
                _uiState.update { it.copy(error = "未获取到加入令牌，请重新验证邀请码") }
                return@launch
            }

            _uiState.update { it.copy(isLoading = true, error = null) }

            android.util.Log.d("MainViewModel", "Joining sign session: sessionId=${sessionInfo.sessionId}, parties=${sessionInfo.parties.size}, joinToken length=${pendingCoSignJoinToken.length}")

            // Join session via gRPC (matching Electron's cosign:joinSession)
            // Use complete parties list from validateInviteCode (matching Electron's behavior)
            val result = repository.joinSignSessionViaGrpc(
                sessionId = sessionInfo.sessionId,
                joinToken = pendingCoSignJoinToken,
                shareId = shareId,
                password = password,
                walletName = sessionInfo.walletName,
                messageHash = sessionInfo.messageHash,
                thresholdT = sessionInfo.thresholdT,
                thresholdN = sessionInfo.thresholdN,
                parties = sessionInfo.parties  // Use complete parties list from validateInviteCode
            )

            result.fold(
                onSuccess = { joinResult ->
                    android.util.Log.d("MainViewModel", "gRPC sign join success: partyIndex=${joinResult.partyIndex}, sessionStatus=${joinResult.sessionStatus}")

                    // Store join info for when session_started event arrives
                    pendingJoinSignInfo = PendingJoinSignInfo(
                        sessionId = sessionInfo.sessionId,
                        partyIndex = joinResult.partyIndex,
                        thresholdT = sessionInfo.thresholdT,
                        thresholdN = sessionInfo.thresholdN,
                        messageHash = sessionInfo.messageHash,
                        shareId = shareId,
                        password = password
                    )

                    _uiState.update { it.copy(isLoading = false) }

                    // If session is already in_progress, trigger sign immediately (Solution B from Electron)
                    if (joinResult.sessionStatus == "in_progress") {
                        android.util.Log.d("MainViewModel", "Sign session already in_progress, triggering sign immediately")
                        startSignAsJoiner()
                    }
                    // Otherwise, wait for session_started event
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "gRPC sign join failed", e)
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    /**
     * Start sign as joiner (called when session_started event is received or session is already in_progress)
     */
    private fun startSignAsJoiner() {
        val signInfo = pendingJoinSignInfo ?: return

        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }

            android.util.Log.d("MainViewModel", "Starting sign as joiner: sessionId=${signInfo.sessionId}, partyIndex=${signInfo.partyIndex}")

            val result = repository.executeSignAsJoiner(
                sessionId = signInfo.sessionId,
                partyIndex = signInfo.partyIndex,
                thresholdT = signInfo.thresholdT,
                thresholdN = signInfo.thresholdN,
                messageHash = signInfo.messageHash,
                shareId = signInfo.shareId,
                password = signInfo.password
            )

            result.fold(
                onSuccess = { signResult ->
                    _coSignSignature.value = signResult.signature
                    _uiState.update {
                        it.copy(
                            isLoading = false,
                            lastSignature = signResult.signature,
                            successMessage = "签名完成！"
                        )
                    }
                    // Clear pending info
                    pendingJoinSignInfo = null
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "Sign execution failed", e)
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    /**
     * Reset CoSign state
     */
    fun resetCoSignState() {
        _coSignSessionInfo.value = null
        _coSignParticipants.value = emptyList()
        _coSignRound.value = 0
        _coSignSignature.value = null
        pendingCoSignInviteCode = ""
        pendingCoSignJoinToken = ""
        pendingJoinSignInfo = null
        // Reset synchronous flag for fresh session
        sessionStartedForSession = null
        // Reset session status to WAITING for fresh start
        repository.resetSessionStatus()
    }

    /**
     * Cancel current session
     */
    fun cancelSession() {
        repository.cancelSession()
        _createdInviteCode.value = null
        _uiState.update { it.copy(isLoading = false) }
    }

    /**
     * Delete a share
     */
    fun deleteShare(id: Long) {
        viewModelScope.launch {
            repository.deleteShare(id)
            // Update wallet count
            _appState.update { state ->
                state.copy(walletCount = (state.walletCount - 1).coerceAtLeast(0))
            }
        }
    }

    // ========== Transaction Records ==========

    private val _transactionRecords = MutableStateFlow<List<com.durian.tssparty.data.local.TransactionRecordEntity>>(emptyList())
    val transactionRecords: StateFlow<List<com.durian.tssparty.data.local.TransactionRecordEntity>> = _transactionRecords.asStateFlow()

    private val _isSyncingHistory = MutableStateFlow(false)
    val isSyncingHistory: StateFlow<Boolean> = _isSyncingHistory.asStateFlow()

    private val _syncResultMessage = MutableStateFlow<String?>(null)
    val syncResultMessage: StateFlow<String?> = _syncResultMessage.asStateFlow()

    fun clearSyncResultMessage() {
        _syncResultMessage.value = null
    }

    /**
     * 加载钱包的交易记录
     */
    fun loadTransactionRecords(shareId: Long) {
        viewModelScope.launch {
            repository.getTransactionRecords(shareId).collect { records ->
                _transactionRecords.value = records
            }
        }
    }

    /**
     * 同步钱包的所有历史交易
     * 首次导入钱包时调用
     */
    fun syncTransactionHistory(shareId: Long, address: String) {
        viewModelScope.launch {
            _isSyncingHistory.value = true
            android.util.Log.d("MainViewModel", "[SYNC] Starting transaction history sync for $address")

            val rpcUrl = _settings.value.kavaRpcUrl
            val networkType = _settings.value.networkType

            val result = repository.syncAllTransactionHistory(shareId, address, rpcUrl, networkType)
            result.fold(
                onSuccess = { count ->
                    android.util.Log.d("MainViewModel", "[SYNC] Synced $count transactions")
                    _syncResultMessage.value = if (count > 0) {
                        "同步完成，新增 $count 条记录"
                    } else {
                        "同步完成，无新记录"
                    }
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "[SYNC] Error syncing: ${e.message}")
                    _syncResultMessage.value = "同步失败: ${e.message}"
                }
            )
            _isSyncingHistory.value = false
        }
    }

    /**
     * 确认所有待处理的交易
     * 应用启动时调用
     */
    fun confirmPendingTransactions() {
        viewModelScope.launch {
            val rpcUrl = _settings.value.kavaRpcUrl
            val pendingRecords = repository.getPendingTransactions()
            android.util.Log.d("MainViewModel", "[TX-CONFIRM] Found ${pendingRecords.size} pending transactions")

            for (record in pendingRecords) {
                repository.confirmTransaction(record.txHash, rpcUrl)
            }
        }
    }

    // ========== Share Export/Import ==========

    private val _exportResult = MutableStateFlow<ExportImportResult?>(null)
    val exportResult: StateFlow<ExportImportResult?> = _exportResult.asStateFlow()

    private val _importResult = MutableStateFlow<ExportImportResult?>(null)
    val importResult: StateFlow<ExportImportResult?> = _importResult.asStateFlow()

    /**
     * Export a share backup
     * @param shareId The ID of the share to export
     * @return The backup JSON string on success
     */
    fun exportShareBackup(shareId: Long, onSuccess: (String) -> Unit) {
        android.util.Log.d("MainViewModel", "[EXPORT] ========== exportShareBackup called ==========")
        android.util.Log.d("MainViewModel", "[EXPORT] shareId: $shareId")
        viewModelScope.launch {
            android.util.Log.d("MainViewModel", "[EXPORT] Setting loading state...")
            _exportResult.value = ExportImportResult(isLoading = true)

            android.util.Log.d("MainViewModel", "[EXPORT] Calling repository.exportShareBackup...")
            val result = repository.exportShareBackup(shareId)
            android.util.Log.d("MainViewModel", "[EXPORT] Repository returned, isSuccess: ${result.isSuccess}")
            result.fold(
                onSuccess = { json ->
                    android.util.Log.d("MainViewModel", "[EXPORT] Export succeeded, json length: ${json.length}")
                    android.util.Log.d("MainViewModel", "[EXPORT] Setting success state and calling onSuccess callback...")
                    _exportResult.value = ExportImportResult(isSuccess = true)
                    android.util.Log.d("MainViewModel", "[EXPORT] Calling onSuccess callback with json...")
                    onSuccess(json)
                    android.util.Log.d("MainViewModel", "[EXPORT] onSuccess callback completed")
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "[EXPORT] Export failed: ${e.message}", e)
                    _exportResult.value = ExportImportResult(error = e.message ?: "导出失败")
                }
            )
            android.util.Log.d("MainViewModel", "[EXPORT] ========== exportShareBackup finished ==========")
        }
    }

    /**
     * Import a share from backup JSON
     * @param backupJson The backup JSON string to import
     */
    fun importShareBackup(backupJson: String) {
        android.util.Log.d("MainViewModel", "[IMPORT] ========== importShareBackup called ==========")
        android.util.Log.d("MainViewModel", "[IMPORT] JSON length: ${backupJson.length}")
        android.util.Log.d("MainViewModel", "[IMPORT] JSON preview: ${backupJson.take(100)}...")
        viewModelScope.launch {
            android.util.Log.d("MainViewModel", "[IMPORT] Setting loading state...")
            _importResult.value = ExportImportResult(isLoading = true)

            android.util.Log.d("MainViewModel", "[IMPORT] Calling repository.importShareBackup...")
            val result = repository.importShareBackup(backupJson)
            android.util.Log.d("MainViewModel", "[IMPORT] Repository returned, isSuccess: ${result.isSuccess}")
            result.fold(
                onSuccess = { share ->
                    android.util.Log.d("MainViewModel", "[IMPORT] Import succeeded:")
                    android.util.Log.d("MainViewModel", "[IMPORT]   - id: ${share.id}")
                    android.util.Log.d("MainViewModel", "[IMPORT]   - address: ${share.address}")
                    android.util.Log.d("MainViewModel", "[IMPORT]   - partyId: ${share.partyId}")
                    _importResult.value = ExportImportResult(
                        isSuccess = true,
                        message = "已成功导入钱包 (${share.address.take(10)}...)"
                    )
                    // Update wallet count
                    android.util.Log.d("MainViewModel", "[IMPORT] Updating wallet count...")
                    _appState.update { state ->
                        state.copy(walletCount = state.walletCount + 1)
                    }
                    // Fetch balance for the imported wallet
                    android.util.Log.d("MainViewModel", "[IMPORT] Fetching balance...")
                    fetchBalanceForShare(share)

                    // Sync transaction history from blockchain (first-time import)
                    android.util.Log.d("MainViewModel", "[IMPORT] Starting transaction history sync...")
                    syncTransactionHistory(share.id, share.address)
                    android.util.Log.d("MainViewModel", "[IMPORT] Import complete!")
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "[IMPORT] Import failed: ${e.message}", e)
                    _importResult.value = ExportImportResult(error = e.message ?: "导入失败")
                }
            )
            android.util.Log.d("MainViewModel", "[IMPORT] ========== importShareBackup finished ==========")
        }
    }

    /**
     * Clear export/import result state
     */
    fun clearExportImportResult() {
        _exportResult.value = null
        _importResult.value = null
    }

    /**
     * Update settings
     */
    fun updateSettings(newSettings: AppSettings) {
        _settings.value = newSettings
        connectToServer(newSettings.messageRouterUrl)
        // Update account service URL in repository
        repository.setAccountServiceUrl(newSettings.accountServiceUrl)
    }

    // ========== Connection Test State ==========

    private val _messageRouterTestResult = MutableStateFlow<ConnectionTestResult?>(null)
    val messageRouterTestResult: StateFlow<ConnectionTestResult?> = _messageRouterTestResult.asStateFlow()

    private val _accountServiceTestResult = MutableStateFlow<ConnectionTestResult?>(null)
    val accountServiceTestResult: StateFlow<ConnectionTestResult?> = _accountServiceTestResult.asStateFlow()

    private val _kavaApiTestResult = MutableStateFlow<ConnectionTestResult?>(null)
    val kavaApiTestResult: StateFlow<ConnectionTestResult?> = _kavaApiTestResult.asStateFlow()

    /**
     * Test Message Router connection
     */
    fun testMessageRouter(serverUrl: String) {
        viewModelScope.launch {
            _messageRouterTestResult.value = null
            val result = repository.testMessageRouter(serverUrl)
            result.fold(
                onSuccess = { latency ->
                    _messageRouterTestResult.value = ConnectionTestResult(
                        success = true,
                        message = "连接成功",
                        latency = latency
                    )
                },
                onFailure = { e ->
                    _messageRouterTestResult.value = ConnectionTestResult(
                        success = false,
                        message = e.message ?: "连接失败"
                    )
                }
            )
        }
    }

    /**
     * Test Account Service connection
     */
    fun testAccountService(serviceUrl: String) {
        viewModelScope.launch {
            _accountServiceTestResult.value = null
            val result = repository.testAccountService(serviceUrl)
            result.fold(
                onSuccess = { latency ->
                    _accountServiceTestResult.value = ConnectionTestResult(
                        success = true,
                        message = "连接成功",
                        latency = latency
                    )
                },
                onFailure = { e ->
                    _accountServiceTestResult.value = ConnectionTestResult(
                        success = false,
                        message = e.message ?: "连接失败"
                    )
                }
            )
        }
    }

    /**
     * Test Kava API connection
     */
    fun testKavaApi(rpcUrl: String) {
        viewModelScope.launch {
            _kavaApiTestResult.value = null
            val result = repository.testKavaApi(rpcUrl)
            result.fold(
                onSuccess = { latency ->
                    _kavaApiTestResult.value = ConnectionTestResult(
                        success = true,
                        message = "连接成功",
                        latency = latency
                    )
                },
                onFailure = { e ->
                    _kavaApiTestResult.value = ConnectionTestResult(
                        success = false,
                        message = e.message ?: "连接失败"
                    )
                }
            )
        }
    }

    /**
     * Clear error message
     */
    fun clearError() {
        _uiState.update { it.copy(error = null) }
    }

    /**
     * Clear success message
     */
    fun clearSuccess() {
        _uiState.update { it.copy(successMessage = null) }
    }

    /**
     * Clear created invite code
     */
    fun clearCreatedInviteCode() {
        _createdInviteCode.value = null
    }

    // Wallet balances cache (KAVA only - deprecated, use walletBalances instead)
    private val _balances = MutableStateFlow<Map<String, String>>(emptyMap())
    val balances: StateFlow<Map<String, String>> = _balances.asStateFlow()

    // Combined wallet balances (KAVA + Green Points)
    private val _walletBalances = MutableStateFlow<Map<String, WalletBalance>>(emptyMap())
    val walletBalances: StateFlow<Map<String, WalletBalance>> = _walletBalances.asStateFlow()

    /**
     * Fetch balance for a wallet using share data (handles both EVM and Cosmos address formats)
     * Now fetches both KAVA and Green Points (绿积分) balances
     */
    fun fetchBalanceForShare(share: ShareRecord) {
        viewModelScope.launch {
            val rpcUrl = _settings.value.kavaRpcUrl
            // Ensure we use EVM address format for RPC calls
            val evmAddress = AddressUtils.getEvmAddress(share.address, share.publicKey)

            // Fetch combined wallet balance (KAVA + Green Points)
            val result = repository.getWalletBalance(evmAddress, rpcUrl)
            result.onSuccess { walletBalance ->
                // Store with original address as key (for UI lookup)
                _walletBalances.update { it + (share.address to walletBalance) }
                // Also update legacy balances map for backward compatibility
                _balances.update { it + (share.address to walletBalance.kavaBalance) }
            }
        }
    }

    /**
     * Fetch balance for a wallet address (for already-EVM addresses)
     */
    fun fetchBalance(address: String) {
        viewModelScope.launch {
            val rpcUrl = _settings.value.kavaRpcUrl
            val result = repository.getWalletBalance(address, rpcUrl)
            result.onSuccess { walletBalance ->
                _walletBalances.update { it + (address to walletBalance) }
                _balances.update { it + (address to walletBalance.kavaBalance) }
            }
        }
    }

    /**
     * Fetch balances for all wallets
     */
    fun fetchAllBalances() {
        viewModelScope.launch {
            shares.value.forEach { share ->
                fetchBalanceForShare(share)
            }
        }
    }

    // ========== Transfer / Sign Session State ==========

    // Transfer state
    private val _transferState = MutableStateFlow(TransferState())
    val transferState: StateFlow<TransferState> = _transferState.asStateFlow()

    // Prepared transaction
    private val _preparedTx = MutableStateFlow<TransactionUtils.PreparedTransaction?>(null)
    val preparedTx: StateFlow<TransactionUtils.PreparedTransaction?> = _preparedTx.asStateFlow()

    // Sign session for transfer
    private val _signSessionId = MutableStateFlow<String?>(null)
    val signSessionId: StateFlow<String?> = _signSessionId.asStateFlow()

    private val _signInviteCode = MutableStateFlow<String?>(null)
    val signInviteCode: StateFlow<String?> = _signInviteCode.asStateFlow()

    private val _signParticipants = MutableStateFlow<List<String>>(emptyList())
    val signParticipants: StateFlow<List<String>> = _signParticipants.asStateFlow()

    private val _signCurrentRound = MutableStateFlow(0)
    val signCurrentRound: StateFlow<Int> = _signCurrentRound.asStateFlow()

    private val _signature = MutableStateFlow<String?>(null)
    val signature: StateFlow<String?> = _signature.asStateFlow()

    private val _txHash = MutableStateFlow<String?>(null)
    val txHash: StateFlow<String?> = _txHash.asStateFlow()

    /**
     * Prepare a transfer transaction
     */
    fun prepareTransfer(shareId: Long, toAddress: String, amount: String, tokenType: TokenType = TokenType.KAVA) {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }
            _transferState.update { it.copy(shareId = shareId, toAddress = toAddress, amount = amount, tokenType = tokenType) }

            val share = repository.getShareById(shareId)
            if (share == null) {
                _uiState.update { it.copy(isLoading = false, error = "钱包不存在") }
                return@launch
            }

            val rpcUrl = _settings.value.kavaRpcUrl
            val chainId = if (_settings.value.networkType == NetworkType.TESTNET) {
                TransactionUtils.KAVA_TESTNET_CHAIN_ID
            } else {
                TransactionUtils.KAVA_MAINNET_CHAIN_ID
            }

            val result = repository.prepareTransaction(
                from = share.address,
                to = toAddress,
                amount = amount,
                rpcUrl = rpcUrl,
                chainId = chainId,
                tokenType = tokenType
            )

            result.fold(
                onSuccess = { tx ->
                    _preparedTx.value = tx
                    _uiState.update { it.copy(isLoading = false) }
                },
                onFailure = { e ->
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    // Store pending sign initiator info for when session_started event arrives
    private var pendingSignInitiatorInfo: PendingSignInitiatorInfo? = null

    /**
     * Create sign session and wait for other participants
     * Matches Electron's cosign:createSession - does NOT start signing immediately
     * Signing is triggered when session_started event is received (via startSignAsInitiator)
     */
    fun initiateSignSession(shareId: Long, password: String, initiatorName: String = "发起者") {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }

            val tx = _preparedTx.value
            if (tx == null) {
                _uiState.update { it.copy(isLoading = false, error = "交易未准备") }
                return@launch
            }

            val result = repository.createSignSession(
                shareId = shareId,
                messageHash = tx.signHash,
                password = password,
                initiatorName = initiatorName
            )

            result.fold(
                onSuccess = { sessionResult ->
                    _signSessionId.value = sessionResult.sessionId
                    _signInviteCode.value = sessionResult.inviteCode
                    _signParticipants.value = listOf(initiatorName)
                    _uiState.update { it.copy(isLoading = false) }

                    // Store pending info for when session_started event arrives
                    // Matching Electron's behavior: don't start signing until session_started
                    pendingSignInitiatorInfo = PendingSignInitiatorInfo(
                        sessionId = sessionResult.sessionId,
                        shareId = shareId,
                        password = password
                    )

                    android.util.Log.d("MainViewModel", "Sign session created, waiting for participants. sessionId=${sessionResult.sessionId}")

                    // Check if session already in_progress (all parties already joined)
                    // This matches Electron's immediate trigger when status === 'in_progress'
                    if (sessionResult.sessionAlreadyInProgress) {
                        android.util.Log.d("MainViewModel", "Session already in_progress, triggering sign immediately")
                        startSigningProcess(sessionResult.sessionId, shareId, password)
                    }
                },
                onFailure = { e ->
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    // ========== 2-of-3 服务器参与选项（新增功能）==========
    // 新增日期：2026-01-27
    // 新增原因：允许 2-of-3 用户在丢失一个设备时，通过服务器参与签名转出资产
    // 影响范围：纯新增，不影响现有 initiateSignSession
    // 回滚方法：删除此方法及相关 UI 代码即可恢复

    /**
     * 创建签名会话（支持选择服务器参与）
     *
     * 新增方法，不修改现有 initiateSignSession
     * 仅在 UI 层判断为 2-of-3 且用户主动勾选时调用此方法
     *
     * @param shareId 钱包 ID
     * @param password 钱包密码
     * @param initiatorName 发起者名称
     * @param includeServerBackup 是否包含服务器备份参与方（新增参数）
     *
     * 使用场景：
     * - 2-of-3 用户丢失一个设备
     * - 用户勾选"包含服务器备份"选项
     * - 使用剩余设备 + 服务器完成签名
     *
     * 安全保障：
     * - UI 层限制仅 2-of-3 显示此选项
     * - 用户主动明确选择
     * - 服务器只有 1 个 key < t=2
     */
    fun initiateSignSessionWithOptions(
        shareId: Long,
        password: String,
        initiatorName: String = "发起者",
        includeServerBackup: Boolean = false  // 新增参数
    ) {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }

            val tx = _preparedTx.value
            if (tx == null) {
                _uiState.update { it.copy(isLoading = false, error = "交易未准备") }
                return@launch
            }

            android.util.Log.d("MainViewModel", "[SIGN-OPTIONS] Initiating sign session with includeServerBackup=$includeServerBackup")

            // 调用新的 repository 方法
            val result = repository.createSignSessionWithOptions(
                shareId = shareId,
                messageHash = tx.signHash,
                password = password,
                initiatorName = initiatorName,
                includeServerBackup = includeServerBackup  // 传递新参数
            )

            result.fold(
                onSuccess = { sessionResult ->
                    _signSessionId.value = sessionResult.sessionId
                    _signInviteCode.value = sessionResult.inviteCode
                    _signParticipants.value = listOf(initiatorName)
                    _uiState.update { it.copy(isLoading = false) }

                    pendingSignInitiatorInfo = PendingSignInitiatorInfo(
                        sessionId = sessionResult.sessionId,
                        shareId = shareId,
                        password = password
                    )

                    android.util.Log.d("MainViewModel", "[SIGN-OPTIONS] Sign session created with server=${includeServerBackup}, sessionId=${sessionResult.sessionId}")

                    if (sessionResult.sessionAlreadyInProgress) {
                        android.util.Log.d("MainViewModel", "[SIGN-OPTIONS] Session already in_progress, triggering sign immediately")
                        startSigningProcess(sessionResult.sessionId, shareId, password)
                    }
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "[SIGN-OPTIONS] Failed to create sign session: ${e.message}")
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }
    // ========== 2-of-3 服务器参与选项结束 ==========

    /**
     * Start sign as initiator (called when session_started event is received)
     * Matches Electron's handleCoSignStart for initiator
     *
     * CRITICAL: This method includes防重入检查 to prevent double execution
     * Race condition fix: TssRepository may have already triggered signing via
     * its session_started handler. This callback serves as a fallback.
     */
    private fun startSignAsInitiator(selectedParties: List<String>) {
        val info = pendingSignInitiatorInfo
        if (info == null) {
            android.util.Log.w("MainViewModel", "startSignAsInitiator called but no pending info")
            return
        }

        // CRITICAL: Prevent double execution if TssRepository already started signing
        // TssRepository sets signingTriggered=true when it auto-triggers from session_started
        if (repository.isSigningTriggered()) {
            android.util.Log.d("MainViewModel", "[RACE-FIX] Signing already triggered by TssRepository, skipping duplicate from MainViewModel")
            return
        }

        android.util.Log.d("MainViewModel", "Starting sign as initiator: sessionId=${info.sessionId}, selectedParties=$selectedParties")
        startSigningProcess(info.sessionId, info.shareId, info.password)
    }

    /**
     * Start the TSS signing process
     */
    private fun startSigningProcess(sessionId: String, shareId: Long, password: String) {
        android.util.Log.d("MainViewModel", "[SIGN] startSigningProcess called: sessionId=$sessionId, shareId=$shareId")
        viewModelScope.launch {
            android.util.Log.d("MainViewModel", "[SIGN] Calling repository.startSigning...")
            val startResult = repository.startSigning(sessionId, shareId, password)
            android.util.Log.d("MainViewModel", "[SIGN] repository.startSigning returned: isSuccess=${startResult.isSuccess}")

            if (startResult.isFailure) {
                android.util.Log.e("MainViewModel", "[SIGN] startSigning FAILED: ${startResult.exceptionOrNull()?.message}")
                _uiState.update { it.copy(error = startResult.exceptionOrNull()?.message) }
                return@launch
            }

            // Wait for signature
            android.util.Log.d("MainViewModel", "[SIGN] startSigning succeeded, calling waitForSignature...")
            val signResult = repository.waitForSignature()
            android.util.Log.d("MainViewModel", "[SIGN] waitForSignature returned: isSuccess=${signResult.isSuccess}")

            signResult.fold(
                onSuccess = { result ->
                    android.util.Log.d("MainViewModel", "[SIGN] Signature received: ${result.signature.take(20)}...")
                    _signature.value = result.signature
                    // IMPORTANT: Reset isLoading so broadcast button is enabled
                    _uiState.update { it.copy(isLoading = false) }
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "[SIGN] waitForSignature FAILED: ${e.message}")
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    /**
     * Broadcast the signed transaction
     */
    fun broadcastTransaction() {
        android.util.Log.d("MainViewModel", "[BROADCAST] broadcastTransaction() called")
        viewModelScope.launch {
            android.util.Log.d("MainViewModel", "[BROADCAST] Starting broadcast...")
            _uiState.update { it.copy(isLoading = true, error = null) }

            val tx = _preparedTx.value
            val sig = _signature.value

            android.util.Log.d("MainViewModel", "[BROADCAST] preparedTx: ${tx?.let { "present" } ?: "null"}")
            android.util.Log.d("MainViewModel", "[BROADCAST] signature: ${sig?.let { "${it.take(20)}..." } ?: "null"}")

            if (tx == null || sig == null) {
                android.util.Log.e("MainViewModel", "[BROADCAST] Missing tx or signature! tx=$tx, sig=$sig")
                _uiState.update { it.copy(isLoading = false, error = "交易或签名缺失") }
                return@launch
            }

            val rpcUrl = _settings.value.kavaRpcUrl
            android.util.Log.d("MainViewModel", "[BROADCAST] Using RPC URL: $rpcUrl")
            val result = repository.broadcastTransaction(tx, sig, rpcUrl)

            android.util.Log.d("MainViewModel", "[BROADCAST] repository.broadcastTransaction returned: isSuccess=${result.isSuccess}")

            result.fold(
                onSuccess = { hash ->
                    android.util.Log.d("MainViewModel", "[BROADCAST] SUCCESS! txHash=$hash")
                    _txHash.value = hash

                    // 保存交易记录到本地数据库
                    val state = _transferState.value
                    android.util.Log.d("MainViewModel", "[BROADCAST] Saving transaction record: shareId=${state.shareId}, tokenType=${state.tokenType}")
                    try {
                        repository.saveTransactionRecord(
                            shareId = state.shareId,
                            fromAddress = tx.from,
                            toAddress = tx.to,
                            amount = state.amount,
                            tokenType = state.tokenType,
                            txHash = hash,
                            gasPrice = tx.gasPrice.toString()
                        )
                        android.util.Log.d("MainViewModel", "[BROADCAST] Transaction record saved successfully")

                        // 启动后台确认交易状态
                        confirmTransactionInBackground(hash, rpcUrl)

                        _uiState.update { it.copy(isLoading = false, successMessage = "交易已广播!") }
                    } catch (e: Exception) {
                        android.util.Log.e("MainViewModel", "[BROADCAST] Failed to save transaction record: ${e.message}", e)
                        _uiState.update { it.copy(isLoading = false, error = "交易已广播但保存记录失败: ${e.message}") }
                    }
                },
                onFailure = { e ->
                    android.util.Log.e("MainViewModel", "[BROADCAST] FAILED: ${e.message}", e)
                    _uiState.update { it.copy(isLoading = false, error = e.message) }
                }
            )
        }
    }

    /**
     * 后台确认交易状态
     * 每 3 秒轮询一次，最多尝试 60 次（3 分钟）
     */
    private fun confirmTransactionInBackground(txHash: String, rpcUrl: String) {
        viewModelScope.launch {
            android.util.Log.d("MainViewModel", "[TX-CONFIRM] Starting background confirmation for $txHash")
            var attempts = 0
            val maxAttempts = 60

            while (attempts < maxAttempts) {
                kotlinx.coroutines.delay(3000)  // 等待 3 秒
                attempts++

                val result = repository.confirmTransaction(txHash, rpcUrl)
                result.fold(
                    onSuccess = { confirmed ->
                        if (confirmed) {
                            android.util.Log.d("MainViewModel", "[TX-CONFIRM] Transaction confirmed after $attempts attempts")
                            return@launch
                        }
                    },
                    onFailure = { e ->
                        android.util.Log.w("MainViewModel", "[TX-CONFIRM] Error checking confirmation: ${e.message}")
                    }
                )
            }
            android.util.Log.w("MainViewModel", "[TX-CONFIRM] Max attempts reached, transaction may still be pending")
        }
    }

    /**
     * Reset transfer state
     */
    fun resetTransferState() {
        _transferState.value = TransferState()
        _preparedTx.value = null
        _signSessionId.value = null
        _signInviteCode.value = null
        _signParticipants.value = emptyList()
        _signCurrentRound.value = 0
        _signature.value = null
        _txHash.value = null
        pendingSignInitiatorInfo = null
        // Reset synchronous flag for fresh session
        sessionStartedForSession = null
        // Reset session status to WAITING for fresh start
        repository.resetSessionStatus()
    }

    /**
     * Get wallet by ID
     */
    fun getWalletById(shareId: Long): ShareRecord? {
        return shares.value.find { it.id == shareId }
    }

    override fun onCleared() {
        super.onCleared()
        repository.cleanup()
    }
}

/**
 * UI State
 */
data class MainUiState(
    val isConnected: Boolean = false,
    val isLoading: Boolean = false,
    val error: String? = null,
    val successMessage: String? = null,
    val lastCreatedAddress: String? = null,
    val lastSignature: String? = null,
    // 5-minute countdown for keygen/sign startup timeout
    // -1 = not counting, 0 = timeout, >0 = remaining seconds
    val countdownSeconds: Long = -1L
)

/**
 * Transfer state
 */
data class TransferState(
    val shareId: Long = 0,
    val toAddress: String = "",
    val amount: String = "",
    val tokenType: TokenType = TokenType.KAVA
)

/**
 * Join keygen session info (from validateInviteCode)
 */
data class JoinKeygenSessionInfo(
    val sessionId: String,
    val walletName: String,
    val thresholdT: Int,
    val thresholdN: Int,
    val initiator: String,
    val currentParticipants: Int,
    val totalParticipants: Int
)

/**
 * CoSign session info (from validateSignInviteCode)
 */
data class CoSignSessionInfo(
    val sessionId: String,
    val keygenSessionId: String,
    val walletName: String,
    val messageHash: String,
    val thresholdT: Int,
    val thresholdN: Int,
    val currentParticipants: Int,
    val parties: List<Participant> = emptyList()  // Complete parties list from API
)

/**
 * Connection test result for Settings screen
 */
data class ConnectionTestResult(
    val success: Boolean,
    val message: String,
    val latency: Long? = null
)

/**
 * Initiator keygen info (stored when creating session)
 */
data class InitiatorKeygenInfo(
    val sessionId: String,
    val partyIndex: Int,
    val walletName: String,
    val thresholdT: Int,
    val thresholdN: Int,
    val participantName: String
)

/**
 * Pending join keygen info (stored when joining session, waiting for session_started)
 * Matches Electron's flow where join happens first, then keygen starts on session_started event
 */
data class PendingJoinKeygenInfo(
    val sessionId: String,
    val partyIndex: Int,
    val thresholdT: Int,
    val thresholdN: Int,
    val walletName: String,
    val password: String
)

/**
 * Pending join sign info (stored when joining sign session, waiting for session_started)
 * Matches Electron's activeCoSignSession
 */
data class PendingJoinSignInfo(
    val sessionId: String,
    val partyIndex: Int,
    val thresholdT: Int,
    val thresholdN: Int,
    val messageHash: String,
    val shareId: Long,
    val password: String
)

/**
 * Pending sign initiator info (stored when creating sign session, waiting for session_started)
 * Matches Electron's activeCoSignSession for initiator
 */
data class PendingSignInitiatorInfo(
    val sessionId: String,
    val shareId: Long,
    val password: String
)

/**
 * Result of export/import operation
 */
data class ExportImportResult(
    val isLoading: Boolean = false,
    val isSuccess: Boolean = false,
    val message: String? = null,
    val error: String? = null
)
