package com.durian.tssparty.data.repository

import android.util.Base64
import com.durian.tssparty.data.local.AppSettingDao
import com.durian.tssparty.data.local.AppSettingEntity
import com.durian.tssparty.data.local.ShareRecordDao
import com.durian.tssparty.data.local.ShareRecordEntity
import com.durian.tssparty.data.local.TssNativeBridge
import com.durian.tssparty.data.remote.GrpcClient
import com.durian.tssparty.data.remote.GrpcConnectionEvent
import com.durian.tssparty.data.remote.GrpcConnectionState
import com.durian.tssparty.data.remote.IncomingMessage
import com.durian.tssparty.data.remote.JoinSessionData
import com.durian.tssparty.data.remote.SessionEventData
import com.durian.tssparty.domain.model.*
import com.durian.tssparty.util.AddressUtils
import com.durian.tssparty.util.TransactionUtils
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Repository for TSS operations
 */
@Singleton
class TssRepository @Inject constructor(
    private val grpcClient: GrpcClient,
    private val tssNativeBridge: TssNativeBridge,
    private val shareRecordDao: ShareRecordDao,
    private val appSettingDao: AppSettingDao
) {
    private val _currentSession = MutableStateFlow<TssSession?>(null)
    val currentSession: StateFlow<TssSession?> = _currentSession.asStateFlow()

    private val _sessionStatus = MutableStateFlow<SessionStatus>(SessionStatus.WAITING)
    val sessionStatus: StateFlow<SessionStatus> = _sessionStatus.asStateFlow()

    // Expose gRPC connection state for UI
    val grpcConnectionState: StateFlow<GrpcConnectionState> = grpcClient.connectionState

    // Expose gRPC connection events for UI notifications
    val grpcConnectionEvents: SharedFlow<GrpcConnectionEvent> = grpcClient.connectionEvents

    // partyId is loaded once from database in registerParty() and cached here
    // This matches Electron's getOrCreatePartyId() pattern
    private lateinit var partyId: String
    private var messageCollectionJob: Job? = null
    private var sessionEventJob: Job? = null

    // Pre-registered session ID for event matching (set before joinSession to avoid race condition)
    // This allows session_started events to be matched even if _currentSession is not yet set
    private var pendingSessionId: String? = null

    // Fallback polling job for session status (handles gRPC stream disconnection on Android)
    // Android gRPC streams can disconnect when app goes to background, so we poll as backup
    private var sessionStatusPollingJob: Job? = null

    // Keygen timeout callback (set by ViewModel)
    // Called when 5-minute polling timeout is reached without session starting
    private var keygenTimeoutCallback: ((String) -> Unit)? = null

    // Countdown tick callback (set by ViewModel)
    // Called every second with remaining seconds for UI countdown display
    private var countdownTickCallback: ((Long) -> Unit)? = null

    // Progress callback (set by ViewModel)
    // Called when TSS protocol progress updates (round/totalRounds)
    private var progressCallback: ((Int, Int) -> Unit)? = null

    // Job for collecting progress from native bridge
    private var progressCollectionJob: Job? = null

    // Repository-level CoroutineScope for background tasks
    // Uses SupervisorJob so individual task failures don't cancel other tasks
    private val repositoryScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    companion object {
        // Polling interval for session status check (matching Electron's 2-second interval)
        private const val POLL_INTERVAL_MS = 2000L
        // Maximum wait time for keygen to start after all parties joined (5 minutes, matching Electron)
        private const val MAX_WAIT_MS = 5 * 60 * 1000L
    }

    /**
     * Get the current party ID
     * Note: This will throw UninitializedPropertyAccessException if called before registerParty()
     */
    fun getPartyId(): String {
        if (!::partyId.isInitialized) {
            throw IllegalStateException("partyId not initialized. Call registerParty() first.")
        }
        return partyId
    }

    // Track current message routing params for reconnection recovery
    private var currentMessageRoutingSessionId: String? = null
    private var currentMessageRoutingPartyIndex: Int? = null

    // Account service URL (configurable via settings)
    private var accountServiceUrl: String = "https://rwaapi.szaiai.com"

    init {
        // Set up reconnection callback to restore streams
        grpcClient.setOnReconnectedCallback {
            android.util.Log.d("TssRepository", "gRPC reconnected, restoring streams...")
            restoreStreamsAfterReconnect()
        }
    }

    /**
     * Restore message and event streams after gRPC reconnection
     */
    private fun restoreStreamsAfterReconnect() {
        val sessionId = currentMessageRoutingSessionId
        val partyIndex = currentMessageRoutingPartyIndex

        // Restore message routing if we had an active session
        if (sessionId != null && partyIndex != null) {
            android.util.Log.d("TssRepository", "Restoring message routing for session: $sessionId")
            startMessageRouting(sessionId, partyIndex)
        }

        // Restore session event subscription
        if (grpcClient.wasEventStreamSubscribed()) {
            android.util.Log.d("TssRepository", "Restoring session event subscription")
            startSessionEventSubscription()
        }
    }

    // HTTP client for API calls
    private val httpClient = okhttp3.OkHttpClient.Builder()
        .connectTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
        .readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
        .build()

    /**
     * Update account service URL from settings
     */
    fun setAccountServiceUrl(url: String) {
        accountServiceUrl = url.trimEnd('/')
    }

    /**
     * Connect to the Message Router
     */
    fun connect(host: String, port: Int) {
        grpcClient.connect(host, port)
    }

    /**
     * Disconnect from the server
     */
    fun disconnect() {
        messageCollectionJob?.cancel()
        sessionEventJob?.cancel()
        sessionStatusPollingJob?.cancel()
        grpcClient.disconnect()
    }

    /**
     * Cleanup all resources when the repository is destroyed.
     * Call this when the app is being destroyed to prevent memory leaks.
     */
    fun cleanup() {
        messageCollectionJob?.cancel()
        sessionEventJob?.cancel()
        sessionStatusPollingJob?.cancel()
        repositoryScope.cancel()
        grpcClient.disconnect()
    }

    /**
     * Check if gRPC is connected
     */
    fun isGrpcConnected(): Boolean = grpcClient.isConnected()

    /**
     * Force reconnect to gRPC server
     */
    fun forceReconnect() {
        grpcClient.forceReconnect()
    }

    /**
     * Get current gRPC connection info
     */
    fun getGrpcConnectionInfo(): Pair<String, Int>? = grpcClient.getConnectionInfo()

    // Session event callback (set by ViewModel)
    private var sessionEventCallback: ((SessionEventData) -> Unit)? = null

    /**
     * Register this party with the router
     * Also subscribes to session events (matching Electron behavior)
     *
     * This method loads or creates a persistent partyId from the database (matching Electron's
     * getOrCreatePartyId pattern). The partyId is loaded ONCE here and cached in memory.
     */
    suspend fun registerParty(): String {
        // Load or create persistent partyId (matching Electron's getOrCreatePartyId)
        val existingPartyId = appSettingDao.getValue("party_id")
        partyId = if (existingPartyId != null) {
            android.util.Log.d("TssRepository", "Loaded existing partyId: $existingPartyId")
            existingPartyId
        } else {
            val newPartyId = UUID.randomUUID().toString()
            appSettingDao.setValue(AppSettingEntity("party_id", newPartyId))
            android.util.Log.d("TssRepository", "Generated new partyId: $newPartyId")
            newPartyId
        }

        grpcClient.registerParty(partyId, "temporary", "1.0.0")

        // Subscribe to session events immediately after registration (like Electron does)
        startSessionEventSubscription()

        return partyId
    }

    /**
     * Start session event subscription (called after registration)
     */
    private fun startSessionEventSubscription() {
        sessionEventJob?.cancel()
        android.util.Log.d("TssRepository", "Starting session event subscription for partyId: $partyId")
        sessionEventJob = repositoryScope.launch {
            grpcClient.subscribeSessionEvents(partyId).collect { event ->
                android.util.Log.d("TssRepository", "=== Session event received ===")
                android.util.Log.d("TssRepository", "  eventType: ${event.eventType}")
                android.util.Log.d("TssRepository", "  sessionId: ${event.sessionId}")
                android.util.Log.d("TssRepository", "  selectedParties: ${event.selectedParties}")

                // Check if this event is for our active session OR pending session
                // (pendingSessionId handles race condition where session_started arrives before _currentSession is set)
                val activeSession = _currentSession.value
                val pendingId = pendingSessionId
                android.util.Log.d("TssRepository", "  activeSession: ${activeSession?.sessionId ?: "null"}")
                android.util.Log.d("TssRepository", "  pendingSessionId: ${pendingId ?: "null"}")

                val matchesActiveSession = activeSession != null && event.sessionId == activeSession.sessionId
                val matchesPendingSession = pendingId != null && event.sessionId == pendingId

                if (matchesActiveSession || matchesPendingSession) {
                    android.util.Log.d("TssRepository", "  → Event matches session! (active=$matchesActiveSession, pending=$matchesPendingSession)")
                    when (event.eventType) {
                        "session_started" -> {
                            android.util.Log.d("TssRepository", "  → Processing session_started event")
                            // Notify callback
                            sessionEventCallback?.invoke(event)
                        }
                        "party_joined", "participant_joined" -> {
                            android.util.Log.d("TssRepository", "  → Processing participant_joined event")
                            sessionEventCallback?.invoke(event)
                        }
                        "all_joined" -> {
                            android.util.Log.d("TssRepository", "  → Processing all_joined event")
                            sessionEventCallback?.invoke(event)
                        }
                        else -> {
                            android.util.Log.d("TssRepository", "  → Unknown event type: ${event.eventType}")
                        }
                    }
                } else {
                    android.util.Log.d("TssRepository", "  → Event does NOT match any session (ignored)")
                    if (activeSession == null && pendingId == null) {
                        android.util.Log.d("TssRepository", "    Reason: both activeSession and pendingSessionId are null")
                    } else if (activeSession != null) {
                        android.util.Log.d("TssRepository", "    Reason: sessionId mismatch (event: ${event.sessionId}, active: ${activeSession.sessionId})")
                    }
                }
            }
        }
    }

    /**
     * Ensure session event subscription is active
     * Called before critical operations (like joining sign session) to ensure
     * the event stream hasn't silently disconnected
     */
    private fun ensureSessionEventSubscriptionActive() {
        // Check if the session event job is still active
        val isActive = sessionEventJob?.isActive == true
        android.util.Log.d("TssRepository", "Checking session event subscription: isActive=$isActive")

        if (!isActive) {
            android.util.Log.w("TssRepository", "Session event subscription is not active, restarting...")
            startSessionEventSubscription()
        } else {
            // Even if the job is "active", the gRPC stream may have silently disconnected
            // Force a restart to ensure we have a fresh connection
            android.util.Log.d("TssRepository", "Refreshing session event subscription to ensure fresh connection")
            startSessionEventSubscription()
        }
    }

    /**
     * Set session event callback (called by ViewModel)
     */
    fun setSessionEventCallback(callback: (SessionEventData) -> Unit) {
        sessionEventCallback = callback
    }

    /**
     * Set keygen timeout callback (called by ViewModel)
     * This callback is invoked when the 5-minute polling timeout is reached
     * without the keygen session starting (matching Electron's checkAndTriggerKeygen timeout)
     */
    fun setKeygenTimeoutCallback(callback: (String) -> Unit) {
        keygenTimeoutCallback = callback
    }

    /**
     * Set countdown tick callback (called by ViewModel)
     * This callback is invoked every second with the remaining seconds
     * for displaying countdown in the UI
     */
    fun setCountdownTickCallback(callback: (Long) -> Unit) {
        countdownTickCallback = callback
    }

    /**
     * Set progress callback (called by ViewModel)
     * This callback is invoked when the TSS protocol progresses through rounds
     * @param callback receives (currentRound, totalRounds)
     */
    fun setProgressCallback(callback: (Int, Int) -> Unit) {
        progressCallback = callback
    }

    /**
     * Start collecting progress from native bridge
     * Called when keygen/sign session starts
     */
    private fun startProgressCollection() {
        // Cancel any existing progress collection
        progressCollectionJob?.cancel()

        android.util.Log.d("TssRepository", "[PROGRESS] Starting progress collection from native bridge")

        progressCollectionJob = repositoryScope.launch {
            tssNativeBridge.progress.collect { (round, totalRounds) ->
                android.util.Log.d("TssRepository", "[PROGRESS] Round $round / $totalRounds")
                withContext(Dispatchers.Main) {
                    progressCallback?.invoke(round, totalRounds)
                }
            }
        }
    }

    /**
     * Stop collecting progress from native bridge
     * Called when session ends or is cancelled
     */
    private fun stopProgressCollection() {
        progressCollectionJob?.cancel()
        progressCollectionJob = null
        android.util.Log.d("TssRepository", "[PROGRESS] Progress collection stopped")
    }

    /**
     * Start polling session status as fallback mechanism
     * This matches Electron's checkAndTriggerKeygen() polling behavior:
     * - Polls every 2 seconds
     * - Times out after 5 minutes
     * - Triggers keygen when session status is "in_progress"
     * - Calls timeout callback if keygen doesn't start within 5 minutes
     *
     * Android needs this because gRPC streams can disconnect when app goes to background,
     * causing session_started events to be missed.
     *
     * @param sessionId The session to poll
     * @param sessionType "keygen" or "sign" - determines which callback to invoke
     */
    fun startSessionStatusPolling(sessionId: String, sessionType: String = "keygen") {
        // Cancel any existing polling job
        sessionStatusPollingJob?.cancel()

        android.util.Log.d("TssRepository", "[POLLING] Starting session status polling for $sessionType session: $sessionId")

        // Notify UI that countdown has started (5 minutes = 300 seconds)
        countdownTickCallback?.invoke(MAX_WAIT_MS / 1000)

        sessionStatusPollingJob = repositoryScope.launch {
            val startTime = System.currentTimeMillis()
            var pollCount = 0
            var lastTickSecond = MAX_WAIT_MS / 1000

            while (isActive && (System.currentTimeMillis() - startTime) < MAX_WAIT_MS) {
                pollCount++
                android.util.Log.d("TssRepository", "[POLLING] Poll #$pollCount for session: $sessionId")

                // Calculate and emit remaining seconds for countdown display
                val elapsedMs = System.currentTimeMillis() - startTime
                val remainingSeconds = (MAX_WAIT_MS - elapsedMs) / 1000
                if (remainingSeconds != lastTickSecond) {
                    lastTickSecond = remainingSeconds
                    withContext(Dispatchers.Main) {
                        countdownTickCallback?.invoke(remainingSeconds)
                    }
                }

                try {
                    val statusResult = getSessionStatus(sessionId)

                    statusResult.fold(
                        onSuccess = { status ->
                            android.util.Log.d("TssRepository", "[POLLING] Session status: ${status.status}, completed: ${status.completedParties}/${status.totalParties}")

                            // Check if session is in_progress (all parties joined and ready)
                            if (status.status == "in_progress") {
                                android.util.Log.d("TssRepository", "[POLLING] Session is in_progress, triggering $sessionType via callback")

                                // Clear countdown when session starts
                                withContext(Dispatchers.Main) {
                                    countdownTickCallback?.invoke(-1L)  // -1 indicates countdown stopped (success)
                                }

                                // Create synthetic session_started event to trigger keygen/sign
                                // This matches Electron's behavior of checking status and triggering manually
                                val eventData = SessionEventData(
                                    eventId = "polling_trigger_${System.currentTimeMillis()}",
                                    eventType = "session_started",
                                    sessionId = sessionId,
                                    thresholdN = status.thresholdN,
                                    thresholdT = status.thresholdT,
                                    selectedParties = status.participants.map { it.partyId },
                                    joinTokens = emptyMap(),
                                    messageHash = null
                                )

                                // Invoke the session event callback on main thread
                                withContext(Dispatchers.Main) {
                                    sessionEventCallback?.invoke(eventData)
                                }

                                // Stop polling after triggering
                                android.util.Log.d("TssRepository", "[POLLING] Triggered $sessionType, stopping polling")
                                return@launch
                            }

                            // Check if session failed or was cancelled
                            if (status.status == "failed" || status.status == "cancelled") {
                                android.util.Log.d("TssRepository", "[POLLING] Session ${status.status}, stopping polling")
                                withContext(Dispatchers.Main) {
                                    countdownTickCallback?.invoke(-1L)  // Clear countdown
                                }
                                return@launch
                            }

                            // Check if session already completed (keygen finished)
                            if (status.status == "completed") {
                                android.util.Log.d("TssRepository", "[POLLING] Session completed, stopping polling")
                                withContext(Dispatchers.Main) {
                                    countdownTickCallback?.invoke(-1L)  // Clear countdown
                                }
                                return@launch
                            }
                        },
                        onFailure = { e ->
                            android.util.Log.w("TssRepository", "[POLLING] Failed to get session status: ${e.message}")
                            // Continue polling on failure (network hiccup, etc.)
                        }
                    )
                } catch (e: Exception) {
                    android.util.Log.w("TssRepository", "[POLLING] Exception during polling: ${e.message}")
                }

                // Wait before next poll
                delay(POLL_INTERVAL_MS)
            }

            // Timeout reached - keygen didn't start within 5 minutes
            if (isActive) {
                val elapsedSeconds = (System.currentTimeMillis() - startTime) / 1000
                android.util.Log.e("TssRepository", "[POLLING] Timeout after ${elapsedSeconds}s waiting for $sessionType to start")

                withContext(Dispatchers.Main) {
                    countdownTickCallback?.invoke(0L)  // Countdown reached zero
                    keygenTimeoutCallback?.invoke("等待 $sessionType 启动超时（5分钟）")
                }
            }
        }
    }

    /**
     * Stop session status polling
     * Called when:
     * - Session starts successfully (keygen/sign triggered)
     * - Session is cancelled
     * - User navigates away
     */
    fun stopSessionStatusPolling() {
        sessionStatusPollingJob?.cancel()
        sessionStatusPollingJob = null
        // Clear countdown display when polling is stopped
        countdownTickCallback?.invoke(-1L)
        android.util.Log.d("TssRepository", "[POLLING] Session status polling stopped")
    }

    /**
     * Get share count for startup check
     */
    suspend fun getShareCount(): Int {
        return shareRecordDao.getShareCount()
    }

    /**
     * Check Kava blockchain health
     */
    suspend fun checkKavaHealth(): Boolean {
        // TODO: Implement actual Kava RPC health check
        return true
    }

    /**
     * Create a new keygen session (as initiator)
     * Calls account-service API: POST /api/v1/co-managed/sessions
     * Then auto-joins the session via gRPC (matching Electron behavior)
     */
    suspend fun createKeygenSession(
        walletName: String,
        thresholdT: Int,
        thresholdN: Int,
        participantName: String
    ): Result<CreateKeygenSessionResult> {
        return withContext(Dispatchers.IO) {
            try {
                val jsonMediaType = "application/json; charset=utf-8".toMediaType()

                // Calculate persistent and external counts (matching Electron)
                // persistent = n - t (platform backup parties)
                // external = t (user-held parties)
                val persistentCount = thresholdN - thresholdT
                val externalCount = thresholdT

                // Build request body matching account-service API (same as Electron)
                val requestBody = com.google.gson.JsonObject().apply {
                    addProperty("wallet_name", walletName)
                    addProperty("threshold_t", thresholdT)
                    addProperty("threshold_n", thresholdN)
                    addProperty("initiator_party_id", partyId)
                    addProperty("initiator_name", participantName)
                    addProperty("persistent_count", persistentCount)
                    addProperty("external_count", externalCount)
                    addProperty("expires_in_seconds", 86400) // 24 hours, same as Electron
                }.toString()

                val request = okhttp3.Request.Builder()
                    .url("$accountServiceUrl/api/v1/co-managed/sessions")
                    .post(requestBody.toRequestBody(jsonMediaType))
                    .header("Content-Type", "application/json")
                    .build()

                android.util.Log.d("TssRepository", "Creating keygen session: $requestBody")

                val response = httpClient.newCall(request).execute()
                val responseBody = response.body?.string()
                    ?: return@withContext Result.failure(Exception("空响应"))

                android.util.Log.d("TssRepository", "Create session response: $responseBody")

                if (!response.isSuccessful) {
                    val errorJson = try {
                        com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                    } catch (e: Exception) { null }
                    val errorMsg = errorJson?.get("message")?.asString
                        ?: errorJson?.get("error")?.asString
                        ?: "HTTP ${response.code}"
                    return@withContext Result.failure(Exception(errorMsg))
                }

                // Parse response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                val sessionId = json.get("session_id").asString
                val inviteCode = json.get("invite_code").asString

                // Get join token - prioritize join_tokens map with partyId, then wildcard, then single join_token
                val joinTokensObj = json.get("join_tokens")?.asJsonObject
                val joinToken = joinTokensObj?.get(partyId)?.asString
                    ?: joinTokensObj?.get("*")?.asString
                    ?: json.get("join_token")?.asString
                    ?: ""

                android.util.Log.d("TssRepository", "Session created: sessionId=$sessionId, inviteCode=$inviteCode, joinToken length=${joinToken.length}")

                // CRITICAL: Set pendingSessionId BEFORE joinSession to avoid race condition
                // This ensures session_started events can be matched even if they arrive
                // before _currentSession is set
                pendingSessionId = sessionId
                android.util.Log.d("TssRepository", "Set pendingSessionId=$sessionId for event matching")

                // Auto-join session via gRPC (matching Electron behavior)
                var partyIndex = 0
                var sessionAlreadyInProgress = false
                if (joinToken.isNotEmpty()) {
                    android.util.Log.d("TssRepository", "Initiator auto-joining session via gRPC...")
                    val joinResult = grpcClient.joinSession(sessionId, partyId, joinToken)

                    if (joinResult.isSuccess) {
                        val joinData = joinResult.getOrThrow()
                        partyIndex = joinData.partyIndex

                        android.util.Log.d("TssRepository", "Initiator joined session: partyIndex=$partyIndex, sessionStatus=${joinData.sessionStatus}")

                        // Update session state
                        val session = TssSession(
                            sessionId = sessionId,
                            sessionType = SessionType.KEYGEN,
                            thresholdT = thresholdT,
                            thresholdN = thresholdN,
                            participants = listOf(Participant(partyId, partyIndex, participantName)),
                            status = SessionStatus.WAITING,
                            inviteCode = inviteCode
                        )
                        _currentSession.value = session
                        _sessionStatus.value = SessionStatus.WAITING

                        // Start message subscription for this session
                        startMessageRouting(sessionId, partyIndex)

                        // Check if session is already in_progress (Solution B from Electron)
                        // This handles the case where all parties join before we check
                        if (joinData.sessionStatus == "in_progress") {
                            android.util.Log.d("TssRepository", "Session already in_progress, will trigger keygen immediately")
                            sessionAlreadyInProgress = true
                        }
                    } else {
                        android.util.Log.w("TssRepository", "Initiator failed to join session via gRPC: ${joinResult.exceptionOrNull()?.message}")
                    }
                } else {
                    android.util.Log.w("TssRepository", "No join token found for initiator partyId: $partyId")
                }

                // If session is already in_progress, notify callback immediately
                if (sessionAlreadyInProgress) {
                    val eventData = SessionEventData(
                        eventId = "immediate_start",
                        eventType = "session_started",
                        sessionId = sessionId,
                        thresholdN = thresholdN,
                        thresholdT = thresholdT,
                        selectedParties = listOf(partyId),
                        joinTokens = emptyMap(),
                        messageHash = null
                    )
                    sessionEventCallback?.invoke(eventData)
                }

                Result.success(CreateKeygenSessionResult(
                    sessionId = sessionId,
                    inviteCode = inviteCode,
                    partyIndex = partyIndex,
                    walletName = walletName,
                    thresholdT = thresholdT,
                    thresholdN = thresholdN
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Create keygen session failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Get all stored shares
     */
    fun getAllShares(): Flow<List<ShareRecord>> {
        return shareRecordDao.getAllShares().map { entities ->
            entities.map { it.toShareRecord() }
        }
    }

    /**
     * Get session status with full participant list
     * Calls account-service API: GET /api/v1/co-managed/sessions/{sessionId}
     * This is critical for getting the complete participant list before starting keygen
     */
    suspend fun getSessionStatus(sessionId: String): Result<SessionStatusResponse> {
        return withContext(Dispatchers.IO) {
            try {
                val request = okhttp3.Request.Builder()
                    .url("$accountServiceUrl/api/v1/co-managed/sessions/$sessionId")
                    .get()
                    .build()

                android.util.Log.d("TssRepository", "Getting session status: $sessionId")

                val response = httpClient.newCall(request).execute()
                val responseBody = response.body?.string()
                    ?: return@withContext Result.failure(Exception("空响应"))

                android.util.Log.d("TssRepository", "Session status response: $responseBody")

                if (!response.isSuccessful) {
                    val errorJson = try {
                        com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                    } catch (e: Exception) { null }
                    val errorMsg = errorJson?.get("message")?.asString
                        ?: errorJson?.get("error")?.asString
                        ?: "HTTP ${response.code}"
                    return@withContext Result.failure(Exception(errorMsg))
                }

                // Parse response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                val status = json.get("status")?.asString ?: ""
                val thresholdT = json.get("threshold_t")?.asInt ?: 2
                val thresholdN = json.get("threshold_n")?.asInt ?: json.get("total_parties")?.asInt ?: 3
                val completedParties = json.get("completed_parties")?.asInt ?: 0
                val totalParties = json.get("total_parties")?.asInt ?: thresholdN
                val sessionType = json.get("session_type")?.asString ?: ""

                // Parse participants array with party_index
                val participantsArray = json.get("participants")?.asJsonArray
                val participants = mutableListOf<ParticipantStatusInfo>()
                participantsArray?.forEach { elem ->
                    val p = elem.asJsonObject
                    participants.add(ParticipantStatusInfo(
                        partyId = p.get("party_id")?.asString ?: "",
                        partyIndex = p.get("party_index")?.asInt ?: 0,
                        status = p.get("status")?.asString ?: ""
                    ))
                }

                android.util.Log.d("TssRepository", "Session status: status=$status, participants=${participants.size}, threshold=$thresholdT-of-$thresholdN")

                Result.success(SessionStatusResponse(
                    sessionId = sessionId,
                    status = status,
                    thresholdT = thresholdT,
                    thresholdN = thresholdN,
                    completedParties = completedParties,
                    totalParties = totalParties,
                    sessionType = sessionType,
                    participants = participants
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Get session status failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Validate an invite code and get session info
     * Calls account-service API: GET /api/v1/co-managed/sessions/by-invite-code/{inviteCode}
     */
    suspend fun validateInviteCode(inviteCode: String): Result<ValidateInviteCodeResult> {
        return withContext(Dispatchers.IO) {
            try {
                val request = okhttp3.Request.Builder()
                    .url("$accountServiceUrl/api/v1/co-managed/sessions/by-invite-code/$inviteCode")
                    .get()
                    .build()

                android.util.Log.d("TssRepository", "Validating invite code: $inviteCode")

                val response = httpClient.newCall(request).execute()
                val responseBody = response.body?.string()
                    ?: return@withContext Result.failure(Exception("空响应"))

                android.util.Log.d("TssRepository", "Validate response: $responseBody")

                if (!response.isSuccessful) {
                    val errorJson = try {
                        com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                    } catch (e: Exception) { null }
                    val errorMsg = errorJson?.get("message")?.asString
                        ?: errorJson?.get("error")?.asString
                        ?: "HTTP ${response.code}"
                    return@withContext Result.failure(Exception(errorMsg))
                }

                // Parse response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                val sessionId = json.get("session_id").asString
                val walletName = json.get("wallet_name")?.asString ?: ""
                val thresholdT = json.get("threshold_t")?.asInt ?: 2
                val thresholdN = json.get("threshold_n")?.asInt ?: 3
                val joinedParties = json.get("joined_parties")?.asInt ?: 0
                val joinToken = json.get("join_token")?.asString ?: ""

                val sessionInfo = SessionInfoResponse(
                    sessionId = sessionId,
                    walletName = walletName,
                    thresholdT = thresholdT,
                    thresholdN = thresholdN,
                    initiator = "发起者", // API may not return this
                    currentParticipants = joinedParties,
                    totalParticipants = thresholdN
                )

                Result.success(ValidateInviteCodeResult(
                    sessionInfo = sessionInfo,
                    joinToken = joinToken
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Validate invite code failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Validate a sign session invite code and get session info
     * Calls account-service API: GET /api/v1/co-managed/sign/by-invite-code/{inviteCode}
     */
    suspend fun validateSignInviteCode(inviteCode: String): Result<ValidateSignInviteCodeResult> {
        return withContext(Dispatchers.IO) {
            try {
                val request = okhttp3.Request.Builder()
                    .url("$accountServiceUrl/api/v1/co-managed/sign/by-invite-code/$inviteCode")
                    .get()
                    .build()

                android.util.Log.d("TssRepository", "Validating sign invite code: $inviteCode")

                val response = httpClient.newCall(request).execute()
                val responseBody = response.body?.string()
                    ?: return@withContext Result.failure(Exception("空响应"))

                android.util.Log.d("TssRepository", "Validate sign response: $responseBody")

                if (!response.isSuccessful) {
                    val errorJson = try {
                        com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                    } catch (e: Exception) { null }
                    val errorMsg = errorJson?.get("message")?.asString
                        ?: errorJson?.get("error")?.asString
                        ?: "HTTP ${response.code}"
                    return@withContext Result.failure(Exception(errorMsg))
                }

                // Parse response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                val sessionId = json.get("session_id").asString
                val keygenSessionId = json.get("keygen_session_id")?.asString ?: ""
                val walletName = json.get("wallet_name")?.asString ?: ""
                val messageHash = json.get("message_hash")?.asString ?: ""
                val thresholdT = json.get("threshold_t")?.asInt ?: 2
                val thresholdN = json.get("threshold_n")?.asInt ?: 3
                val joinedCount = json.get("joined_count")?.asInt ?: 0
                val joinToken = json.get("join_token")?.asString ?: ""

                // Parse parties array (matching Electron's sessionInfo.parties)
                val partiesArray = json.get("parties")?.asJsonArray
                val parties = mutableListOf<Participant>()
                partiesArray?.forEach { elem ->
                    val p = elem.asJsonObject
                    parties.add(Participant(
                        partyId = p.get("party_id")?.asString ?: "",
                        partyIndex = p.get("party_index")?.asInt ?: 0,
                        name = "参与方 ${(p.get("party_index")?.asInt ?: 0) + 1}"
                    ))
                }

                android.util.Log.d("TssRepository", "[CO-SIGN] validateSignInviteCode: found ${parties.size} parties")

                val signSessionInfo = SignSessionInfoResponse(
                    sessionId = sessionId,
                    keygenSessionId = keygenSessionId,
                    walletName = walletName,
                    messageHash = messageHash,
                    thresholdT = thresholdT,
                    thresholdN = thresholdN,
                    currentParticipants = joinedCount,
                    parties = parties
                )

                Result.success(ValidateSignInviteCodeResult(
                    signSessionInfo = signSessionInfo,
                    joinToken = joinToken
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Validate sign invite code failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Join a keygen session via gRPC only (matching Electron's grpc:joinSession)
     * This does NOT start keygen - that happens when session_started event is received
     *
     * Flow (matching Electron):
     * 1. Call grpcClient.joinSession(sessionId, partyId, joinToken)
     * 2. Set up activeKeygenSession state
     * 3. Prepare for keygen (subscribe to messages)
     * 4. Return - keygen will start when session_started event is received
     */
    suspend fun joinKeygenSessionViaGrpc(
        sessionId: String,
        joinToken: String,
        walletName: String,
        thresholdT: Int,
        thresholdN: Int
    ): Result<JoinKeygenViaGrpcResult> {
        return withContext(Dispatchers.IO) {
            try {
                android.util.Log.d("TssRepository", "Joining keygen session via gRPC: sessionId=$sessionId, joinToken length=${joinToken.length}")

                // CRITICAL: Set pendingSessionId BEFORE joinSession to avoid race condition
                // This ensures session_started events can be matched even if they arrive
                // before _currentSession is set
                pendingSessionId = sessionId
                android.util.Log.d("TssRepository", "Set pendingSessionId=$sessionId for event matching (joiner)")

                // Join session via gRPC (matching Electron's grpcClient.joinSession)
                val joinResult = grpcClient.joinSession(sessionId, partyId, joinToken)
                if (joinResult.isFailure) {
                    android.util.Log.e("TssRepository", "gRPC join failed", joinResult.exceptionOrNull())
                    return@withContext Result.failure(joinResult.exceptionOrNull()!!)
                }

                val sessionData = joinResult.getOrThrow()
                val myPartyIndex = sessionData.partyIndex

                android.util.Log.d("TssRepository", "gRPC join successful: partyIndex=$myPartyIndex, sessionStatus=${sessionData.sessionStatus}")

                // Build participants list (matching Electron's logic)
                val participants = sessionData.participants.toMutableList()
                participants.add(Participant(partyId, myPartyIndex, "我"))
                participants.sortBy { it.partyIndex }

                // Set up active keygen session state (matching Electron's activeKeygenSession)
                val session = TssSession(
                    sessionId = sessionId,
                    sessionType = SessionType.KEYGEN,
                    thresholdT = thresholdT,
                    thresholdN = thresholdN,
                    participants = participants,
                    status = SessionStatus.WAITING,
                    inviteCode = null
                )
                _currentSession.value = session
                _sessionStatus.value = SessionStatus.WAITING

                // Start message subscription for this session (matching Electron's prepareForKeygen)
                startMessageRouting(sessionId, myPartyIndex)

                android.util.Log.d("TssRepository", "Session state set, waiting for session_started event")

                Result.success(JoinKeygenViaGrpcResult(
                    partyIndex = myPartyIndex,
                    sessionStatus = sessionData.sessionStatus
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Join keygen session via gRPC failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Execute keygen as joiner (called when session_started event is received)
     * This is the second part of the join flow, triggered by session_started event
     */
    suspend fun executeKeygenAsJoiner(
        sessionId: String,
        partyIndex: Int,
        thresholdT: Int,
        thresholdN: Int,
        password: String
    ): Result<ShareRecord> = coroutineScope {
        try {
            val session = _currentSession.value
            if (session == null || session.sessionId != sessionId) {
                return@coroutineScope Result.failure(Exception("No active session or session mismatch"))
            }

            android.util.Log.d("TssRepository", "Executing keygen as joiner: sessionId=$sessionId, partyIndex=$partyIndex")

            // CRITICAL: Fetch complete participant list from backend before starting keygen
            // This ensures we have all participants even if some joined after we did
            val statusResult = getSessionStatus(sessionId)
            if (statusResult.isFailure) {
                android.util.Log.e("TssRepository", "Failed to get session status before keygen", statusResult.exceptionOrNull())
                return@coroutineScope Result.failure(statusResult.exceptionOrNull()!!)
            }

            val sessionStatus = statusResult.getOrThrow()

            // Build complete participant list from backend response
            val updatedParticipants = mutableListOf<Participant>()
            for (p in sessionStatus.participants) {
                val name = if (p.partyId == partyId) {
                    session.participants.find { it.partyId == partyId }?.name ?: "我"
                } else {
                    session.participants.find { it.partyId == p.partyId }?.name ?: "参与方 ${p.partyIndex + 1}"
                }
                updatedParticipants.add(Participant(p.partyId, p.partyIndex, name))
            }

            // Find my party index from the updated list (may differ from initial partyIndex if backend assigned differently)
            val myInfo = updatedParticipants.find { it.partyId == partyId }
            val actualPartyIndex = myInfo?.partyIndex ?: partyIndex

            android.util.Log.d("TssRepository", "Joiner updated participants: ${updatedParticipants.map { "${it.partyId.take(8)}:${it.partyIndex}" }}")
            android.util.Log.d("TssRepository", "My actual party index: $actualPartyIndex (original: $partyIndex)")

            // Update the session with complete participant list
            _currentSession.value = session.copy(
                participants = updatedParticipants,
                thresholdT = sessionStatus.thresholdT,
                thresholdN = sessionStatus.thresholdN
            )

            // Use thresholds from backend (they may be more accurate)
            val actualThresholdT = sessionStatus.thresholdT
            val actualThresholdN = sessionStatus.thresholdN

            // Start TSS keygen with complete participant list
            val startResult = tssNativeBridge.startKeygen(
                sessionId = sessionId,
                partyId = partyId,
                partyIndex = actualPartyIndex,
                thresholdT = actualThresholdT,
                thresholdN = actualThresholdN,
                participants = updatedParticipants,
                password = password
            )

            if (startResult.isFailure) {
                return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
            }

            // Start collecting progress from native bridge
            startProgressCollection()

            _sessionStatus.value = SessionStatus.IN_PROGRESS

            // Mark ready
            grpcClient.markPartyReady(sessionId, partyId)

            // Wait for keygen result
            val keygenResult = tssNativeBridge.waitForKeygenResult(password)
            if (keygenResult.isFailure) {
                stopProgressCollection()
                _sessionStatus.value = SessionStatus.FAILED
                return@coroutineScope Result.failure(keygenResult.exceptionOrNull()!!)
            }

            val result = keygenResult.getOrThrow()

            // Derive address from public key
            val publicKeyBytes = Base64.decode(result.publicKey, Base64.NO_WRAP)
            val address = AddressUtils.deriveEvmAddress(publicKeyBytes)

            // Save share record (use actual thresholds and party index from backend)
            val shareEntity = ShareRecordEntity(
                sessionId = sessionId,
                publicKey = result.publicKey,
                encryptedShare = result.encryptedShare,
                thresholdT = actualThresholdT,
                thresholdN = actualThresholdN,
                partyIndex = actualPartyIndex,
                address = address
            )
            val id = shareRecordDao.insertShare(shareEntity)

            // Report completion
            grpcClient.reportCompletion(sessionId, partyId, publicKeyBytes)

            stopProgressCollection()
            _sessionStatus.value = SessionStatus.COMPLETED
            pendingSessionId = null  // Clear pending session ID on completion

            android.util.Log.d("TssRepository", "Keygen as joiner completed: address=$address, partyIndex=$actualPartyIndex")

            Result.success(shareEntity.copy(id = id).toShareRecord())

        } catch (e: Exception) {
            android.util.Log.e("TssRepository", "Execute keygen as joiner failed", e)
            stopProgressCollection()
            _sessionStatus.value = SessionStatus.FAILED
            pendingSessionId = null  // Clear pending session ID on failure
            Result.failure(e)
        }
    }

    /**
     * Join a sign session via gRPC only (matching Electron's cosign:joinSession flow)
     * This does NOT start signing - that happens when session_started event is received
     *
     * Flow (matching Electron):
     * 1. Validate share exists and password is correct
     * 2. Call grpcClient.joinSession(sessionId, partyId, joinToken)
     * 3. Set up activeCoSignSession state
     * 4. Start message subscription (prepareForSign)
     * 5. If session is already in_progress, return that status so caller can trigger sign
     * 6. Otherwise return - sign will start when session_started event is received
     */
    suspend fun joinSignSessionViaGrpc(
        sessionId: String,
        joinToken: String,
        shareId: Long,
        password: String,
        walletName: String,
        messageHash: String,
        thresholdT: Int,
        thresholdN: Int,
        parties: List<Participant>
    ): Result<JoinSignViaGrpcResult> {
        return withContext(Dispatchers.IO) {
            try {
                android.util.Log.d("TssRepository", "Joining sign session via gRPC: sessionId=$sessionId")

                // Validate share exists (matching Electron)
                val shareEntity = shareRecordDao.getShareById(shareId)
                    ?: return@withContext Result.failure(Exception("Share not found"))

                // Note: Password is verified during actual sign execution, same as Electron

                // CRITICAL: Set pendingSessionId BEFORE joinSession to avoid race condition
                // This ensures session_started events can be matched even if they arrive
                // before _currentSession is set
                pendingSessionId = sessionId
                android.util.Log.d("TssRepository", "Set pendingSessionId=$sessionId for event matching (sign joiner)")

                // Join session via gRPC (matching Electron's grpcClient.joinSession)
                val joinResult = grpcClient.joinSession(sessionId, partyId, joinToken)
                if (joinResult.isFailure) {
                    android.util.Log.e("TssRepository", "gRPC sign join failed", joinResult.exceptionOrNull())
                    return@withContext Result.failure(joinResult.exceptionOrNull()!!)
                }

                val sessionData = joinResult.getOrThrow()
                // Use party_index from gRPC response (matching Electron's result.party_index)
                // This is more authoritative than local share's partyIndex
                val myPartyIndex = sessionData.partyIndex

                android.util.Log.d("TssRepository", "gRPC sign join successful: partyIndex=$myPartyIndex (from gRPC), localPartyIndex=${shareEntity.partyIndex}, sessionStatus=${sessionData.sessionStatus}")

                // Build participants list (matching Electron's logic)
                // Prefer using parties from validateInviteCode (complete list)
                val participants = if (parties.isNotEmpty()) {
                    parties.toMutableList()
                } else {
                    // Fallback: use other_parties + self
                    val list = sessionData.participants.toMutableList()
                    list.add(Participant(partyId, myPartyIndex, "我"))
                    list.sortBy { it.partyIndex }
                    list
                }

                // Set up active sign session state (matching Electron's activeCoSignSession)
                val session = TssSession(
                    sessionId = sessionId,
                    sessionType = SessionType.SIGN,
                    thresholdT = thresholdT,
                    thresholdN = thresholdN,
                    participants = participants,
                    status = SessionStatus.WAITING,
                    inviteCode = null,
                    messageHash = messageHash
                )
                _currentSession.value = session
                _sessionStatus.value = SessionStatus.WAITING

                // Start message subscription (matching Electron's prepareForSign)
                startMessageRouting(sessionId, myPartyIndex)

                // CRITICAL: Ensure session event subscription is active
                // The event stream may have silently disconnected, so refresh it
                ensureSessionEventSubscriptionActive()

                android.util.Log.d("TssRepository", "Sign session state set, waiting for session_started event or in_progress status")

                Result.success(JoinSignViaGrpcResult(
                    partyIndex = myPartyIndex,
                    sessionStatus = sessionData.sessionStatus,
                    shareId = shareId
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Join sign session via gRPC failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Execute sign as joiner (called when session_started event is received or session is already in_progress)
     * This is the second part of the join sign flow
     */
    suspend fun executeSignAsJoiner(
        sessionId: String,
        partyIndex: Int,
        thresholdT: Int,
        thresholdN: Int,
        messageHash: String,
        shareId: Long,
        password: String
    ): Result<SignResult> = coroutineScope {
        try {
            val session = _currentSession.value
            if (session == null || session.sessionId != sessionId) {
                return@coroutineScope Result.failure(Exception("No active session or session mismatch"))
            }

            android.util.Log.d("TssRepository", "Executing sign as joiner: sessionId=$sessionId, partyIndex=$partyIndex")

            // Get share record
            val shareEntity = shareRecordDao.getShareById(shareId)
                ?: return@coroutineScope Result.failure(Exception("Share not found"))

            // Update session status
            _sessionStatus.value = SessionStatus.IN_PROGRESS

            // Get all participants from session
            val allParticipants = session.participants

            android.util.Log.d("TssRepository", "Starting TSS sign with ${allParticipants.size} participants, thresholdT=$thresholdT")

            // Start TSS sign
            // Remove 0x prefix from messageHash - TSS library expects pure hex
            val cleanMessageHash = if (messageHash.startsWith("0x") || messageHash.startsWith("0X")) {
                messageHash.substring(2)
            } else {
                messageHash
            }
            android.util.Log.d("TssRepository", "Starting TSS sign with cleanMessageHash=${cleanMessageHash.take(20)}...")
            val startResult = tssNativeBridge.startSign(
                sessionId = sessionId,
                partyId = partyId,
                partyIndex = partyIndex,
                thresholdT = thresholdT,
                thresholdN = shareEntity.thresholdN,  // Use original N from keygen
                participants = allParticipants,
                messageHash = cleanMessageHash,
                shareData = shareEntity.encryptedShare,
                password = password
            )

            if (startResult.isFailure) {
                _sessionStatus.value = SessionStatus.FAILED
                return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
            }

            // Start collecting progress from native bridge
            startProgressCollection()

            // Mark ready
            grpcClient.markPartyReady(sessionId, partyId)

            // Wait for sign result
            val signResult = tssNativeBridge.waitForSignResult()
            if (signResult.isFailure) {
                stopProgressCollection()
                _sessionStatus.value = SessionStatus.FAILED
                return@coroutineScope Result.failure(signResult.exceptionOrNull()!!)
            }

            val result = signResult.getOrThrow()

            // Report completion
            val signatureBytes = android.util.Base64.decode(result.signature, android.util.Base64.NO_WRAP)
            grpcClient.reportCompletion(sessionId, partyId, signature = signatureBytes)

            stopProgressCollection()
            _sessionStatus.value = SessionStatus.COMPLETED
            pendingSessionId = null  // Clear pending session ID on completion
            messageCollectionJob?.cancel()

            android.util.Log.d("TssRepository", "Sign as joiner completed: signature=${result.signature.take(20)}...")

            Result.success(result)

        } catch (e: Exception) {
            android.util.Log.e("TssRepository", "Execute sign as joiner failed", e)
            stopProgressCollection()
            _sessionStatus.value = SessionStatus.FAILED
            pendingSessionId = null  // Clear pending session ID on failure
            Result.failure(e)
        }
    }

    /**
     * Join a keygen session and execute keygen protocol (DEPRECATED - old flow)
     * First calls account-service to get session info and join token, then joins via gRPC
     * @deprecated Use joinKeygenSessionViaGrpc + executeKeygenAsJoiner instead
     */
    @Deprecated("Use joinKeygenSessionViaGrpc + executeKeygenAsJoiner for Electron-compatible flow")
    suspend fun joinKeygenSession(
        inviteCode: String,
        password: String
    ): Result<ShareRecord> = coroutineScope {
        try {
            android.util.Log.d("TssRepository", "Joining keygen session with invite code: $inviteCode")

            // Step 1: Call account-service API to join session and get party_index
            val joinApiResult = joinSessionViaApi(inviteCode)
            if (joinApiResult.isFailure) {
                return@coroutineScope Result.failure(joinApiResult.exceptionOrNull()!!)
            }
            val apiJoinData = joinApiResult.getOrThrow()

            android.util.Log.d("TssRepository", "API join successful: sessionId=${apiJoinData.sessionId}, partyIndex=${apiJoinData.partyIndex}")

            // Step 2: Join session via gRPC for message routing
            // Use the join_token from API response
            val joinResult = grpcClient.joinSession(apiJoinData.sessionId, partyId, apiJoinData.joinToken)
            if (joinResult.isFailure) {
                android.util.Log.e("TssRepository", "gRPC join failed", joinResult.exceptionOrNull())
                return@coroutineScope Result.failure(joinResult.exceptionOrNull()!!)
            }

            val sessionData = joinResult.getOrThrow()

            // Use party_index from API response (more reliable than gRPC response)
            val myPartyIndex = apiJoinData.partyIndex

            // Update session state
            val session = TssSession(
                sessionId = apiJoinData.sessionId,
                sessionType = SessionType.KEYGEN,
                thresholdT = apiJoinData.thresholdT,
                thresholdN = apiJoinData.thresholdN,
                participants = sessionData.participants,
                status = SessionStatus.WAITING,
                inviteCode = inviteCode
            )
            _currentSession.value = session
            _sessionStatus.value = SessionStatus.WAITING

            // Add self to participants
            val allParticipants = sessionData.participants + Participant(partyId, myPartyIndex)

            // Start TSS keygen
            val startResult = tssNativeBridge.startKeygen(
                sessionId = apiJoinData.sessionId,
                partyId = partyId,
                partyIndex = myPartyIndex,
                thresholdT = apiJoinData.thresholdT,
                thresholdN = apiJoinData.thresholdN,
                participants = allParticipants,
                password = password
            )

            if (startResult.isFailure) {
                return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
            }

            _sessionStatus.value = SessionStatus.IN_PROGRESS

            // Start message routing
            startMessageRouting(apiJoinData.sessionId, myPartyIndex)

            // Mark ready
            grpcClient.markPartyReady(apiJoinData.sessionId, partyId)

            // Wait for keygen result
            val keygenResult = tssNativeBridge.waitForKeygenResult(password)
            if (keygenResult.isFailure) {
                _sessionStatus.value = SessionStatus.FAILED
                return@coroutineScope Result.failure(keygenResult.exceptionOrNull()!!)
            }

            val result = keygenResult.getOrThrow()

            // Derive address from public key
            val publicKeyBytes = Base64.decode(result.publicKey, Base64.NO_WRAP)
            val address = AddressUtils.deriveEvmAddress(publicKeyBytes)

            // Save share record
            val shareEntity = ShareRecordEntity(
                sessionId = apiJoinData.sessionId,
                publicKey = result.publicKey,
                encryptedShare = result.encryptedShare,
                thresholdT = apiJoinData.thresholdT,
                thresholdN = apiJoinData.thresholdN,
                partyIndex = myPartyIndex,
                address = address
            )
            val id = shareRecordDao.insertShare(shareEntity)

            // Report completion
            grpcClient.reportCompletion(apiJoinData.sessionId, partyId, publicKeyBytes)

            _sessionStatus.value = SessionStatus.COMPLETED
            messageCollectionJob?.cancel()

            Result.success(shareEntity.copy(id = id).toShareRecord())

        } catch (e: Exception) {
            android.util.Log.e("TssRepository", "Join keygen session failed", e)
            _sessionStatus.value = SessionStatus.FAILED
            Result.failure(e)
        }
    }

    /**
     * Join session via account-service HTTP API
     * POST /api/v1/co-managed/sessions/{sessionId}/join
     */
    private suspend fun joinSessionViaApi(inviteCode: String): Result<ApiJoinSessionData> {
        return withContext(Dispatchers.IO) {
            try {
                // First, get session info by invite code to get session_id and join_token
                val sessionInfoResult = validateInviteCode(inviteCode)
                if (sessionInfoResult.isFailure) {
                    return@withContext Result.failure(sessionInfoResult.exceptionOrNull()!!)
                }
                val sessionInfo = sessionInfoResult.getOrThrow()
                val sessionId = sessionInfo.sessionInfo.sessionId
                val joinToken = sessionInfo.joinToken

                android.util.Log.d("TssRepository", "Got session info: sessionId=$sessionId, joinToken=$joinToken")

                // Now call join API
                val jsonMediaType = "application/json; charset=utf-8".toMediaType()
                val requestBody = com.google.gson.JsonObject().apply {
                    addProperty("party_id", partyId)
                    addProperty("join_token", joinToken)
                    addProperty("device_type", "mobile")
                    addProperty("device_id", partyId)
                }.toString()

                val request = okhttp3.Request.Builder()
                    .url("$accountServiceUrl/api/v1/co-managed/sessions/$sessionId/join")
                    .post(requestBody.toRequestBody(jsonMediaType))
                    .header("Content-Type", "application/json")
                    .build()

                android.util.Log.d("TssRepository", "Joining session via API: $requestBody")

                val response = httpClient.newCall(request).execute()
                val responseBody = response.body?.string()
                    ?: return@withContext Result.failure(Exception("空响应"))

                android.util.Log.d("TssRepository", "Join API response: $responseBody")

                if (!response.isSuccessful) {
                    val errorJson = try {
                        com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                    } catch (e: Exception) { null }
                    val errorMsg = errorJson?.get("message")?.asString
                        ?: errorJson?.get("error")?.asString
                        ?: "HTTP ${response.code}"
                    return@withContext Result.failure(Exception(errorMsg))
                }

                // Parse response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                val partyIndex = json.get("party_index")?.asInt ?: 0
                val sessionInfoJson = json.get("session_info")?.asJsonObject
                val thresholdT = sessionInfoJson?.get("threshold_t")?.asInt ?: sessionInfo.sessionInfo.thresholdT
                val thresholdN = sessionInfoJson?.get("threshold_n")?.asInt ?: sessionInfo.sessionInfo.thresholdN

                Result.success(ApiJoinSessionData(
                    sessionId = sessionId,
                    partyIndex = partyIndex,
                    thresholdT = thresholdT,
                    thresholdN = thresholdN,
                    joinToken = joinToken
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Join session via API failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Join a sign session and execute sign protocol
     * First calls account-service to get session info and join, then joins via gRPC
     */
    suspend fun joinSignSession(
        inviteCode: String,
        shareId: Long,
        password: String
    ): Result<SignResult> = coroutineScope {
        try {
            android.util.Log.d("TssRepository", "Joining sign session with invite code: $inviteCode")

            // Get share record
            val shareEntity = shareRecordDao.getShareById(shareId)
                ?: return@coroutineScope Result.failure(Exception("Share not found"))

            // Step 1: Call account-service API to join sign session and get party info
            val joinApiResult = joinSignSessionViaApi(inviteCode, shareEntity.partyIndex)
            if (joinApiResult.isFailure) {
                return@coroutineScope Result.failure(joinApiResult.exceptionOrNull()!!)
            }
            val apiJoinData = joinApiResult.getOrThrow()

            android.util.Log.d("TssRepository", "API sign join successful: sessionId=${apiJoinData.sessionId}, partyIndex=${apiJoinData.partyIndex}, messageHash=${apiJoinData.messageHash}")

            // Step 2: Join session via gRPC for message routing
            val joinResult = grpcClient.joinSession(apiJoinData.sessionId, partyId, apiJoinData.joinToken)
            if (joinResult.isFailure) {
                android.util.Log.e("TssRepository", "gRPC join failed", joinResult.exceptionOrNull())
                return@coroutineScope Result.failure(joinResult.exceptionOrNull()!!)
            }

            val sessionData = joinResult.getOrThrow()

            // Use party_index from API response (more reliable)
            val myPartyIndex = apiJoinData.partyIndex

            // Update session state
            val session = TssSession(
                sessionId = apiJoinData.sessionId,
                sessionType = SessionType.SIGN,
                thresholdT = apiJoinData.thresholdT,
                thresholdN = apiJoinData.thresholdN,
                participants = sessionData.participants,
                status = SessionStatus.WAITING,
                inviteCode = inviteCode,
                messageHash = apiJoinData.messageHash
            )
            _currentSession.value = session
            _sessionStatus.value = SessionStatus.WAITING

            // Add self to participants
            val allParticipants = sessionData.participants + Participant(partyId, myPartyIndex)

            // Start TSS sign
            val startResult = tssNativeBridge.startSign(
                sessionId = apiJoinData.sessionId,
                partyId = partyId,
                partyIndex = myPartyIndex,
                thresholdT = apiJoinData.thresholdT,
                thresholdN = shareEntity.thresholdN,  // Use original N from keygen
                participants = allParticipants,
                messageHash = apiJoinData.messageHash,
                shareData = shareEntity.encryptedShare,
                password = password
            )

            if (startResult.isFailure) {
                return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
            }

            _sessionStatus.value = SessionStatus.IN_PROGRESS

            // Start message routing
            startMessageRouting(apiJoinData.sessionId, myPartyIndex)

            // Mark ready
            grpcClient.markPartyReady(apiJoinData.sessionId, partyId)

            // Wait for sign result
            val signResult = tssNativeBridge.waitForSignResult()
            if (signResult.isFailure) {
                _sessionStatus.value = SessionStatus.FAILED
                return@coroutineScope Result.failure(signResult.exceptionOrNull()!!)
            }

            val result = signResult.getOrThrow()

            // Report completion
            val signatureBytes = Base64.decode(result.signature, Base64.NO_WRAP)
            grpcClient.reportCompletion(apiJoinData.sessionId, partyId, signature = signatureBytes)

            _sessionStatus.value = SessionStatus.COMPLETED
            messageCollectionJob?.cancel()

            Result.success(result)

        } catch (e: Exception) {
            android.util.Log.e("TssRepository", "Join sign session failed", e)
            _sessionStatus.value = SessionStatus.FAILED
            Result.failure(e)
        }
    }

    /**
     * Join sign session via account-service HTTP API
     * Uses validateSignInviteCode to get session info, then joins
     */
    private suspend fun joinSignSessionViaApi(inviteCode: String, partyIndex: Int): Result<ApiJoinSignSessionData> {
        return withContext(Dispatchers.IO) {
            try {
                // First, get sign session info by invite code
                val sessionInfoResult = validateSignInviteCode(inviteCode)
                if (sessionInfoResult.isFailure) {
                    return@withContext Result.failure(sessionInfoResult.exceptionOrNull()!!)
                }
                val sessionInfo = sessionInfoResult.getOrThrow()
                val signSessionInfo = sessionInfo.signSessionInfo
                val sessionId = signSessionInfo.sessionId
                val joinToken = sessionInfo.joinToken

                android.util.Log.d("TssRepository", "Got sign session info: sessionId=$sessionId, messageHash=${signSessionInfo.messageHash}, joinToken=$joinToken")

                // Now call join API (same endpoint as keygen join, but for sign sessions)
                val jsonMediaType = "application/json; charset=utf-8".toMediaType()
                val requestBody = com.google.gson.JsonObject().apply {
                    addProperty("party_id", partyId)
                    addProperty("join_token", joinToken)
                    addProperty("party_index", partyIndex)
                    addProperty("device_type", "mobile")
                    addProperty("device_id", partyId)
                }.toString()

                val request = okhttp3.Request.Builder()
                    .url("$accountServiceUrl/api/v1/co-managed/sign/$sessionId/join")
                    .post(requestBody.toRequestBody(jsonMediaType))
                    .header("Content-Type", "application/json")
                    .build()

                android.util.Log.d("TssRepository", "Joining sign session via API: $requestBody")

                val response = httpClient.newCall(request).execute()
                val responseBody = response.body?.string()
                    ?: return@withContext Result.failure(Exception("空响应"))

                android.util.Log.d("TssRepository", "Join sign API response: $responseBody")

                if (!response.isSuccessful) {
                    val errorJson = try {
                        com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                    } catch (e: Exception) { null }
                    val errorMsg = errorJson?.get("message")?.asString
                        ?: errorJson?.get("error")?.asString
                        ?: "HTTP ${response.code}"
                    return@withContext Result.failure(Exception(errorMsg))
                }

                // Parse response - extract party_index if provided, otherwise use the one from share
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                val responsePartyIndex = json.get("party_index")?.asInt ?: partyIndex

                Result.success(ApiJoinSignSessionData(
                    sessionId = sessionId,
                    partyIndex = responsePartyIndex,
                    thresholdT = signSessionInfo.thresholdT,
                    thresholdN = signSessionInfo.thresholdN,
                    messageHash = signSessionInfo.messageHash,
                    joinToken = joinToken
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Join sign session via API failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Start message routing between TSS and gRPC
     */
    private fun startMessageRouting(sessionId: String, partyIndex: Int) {
        // Save params for reconnection recovery
        currentMessageRoutingSessionId = sessionId
        currentMessageRoutingPartyIndex = partyIndex

        messageCollectionJob?.cancel()
        messageCollectionJob = repositoryScope.launch {
            // Collect outgoing messages from TSS and route via gRPC
            launch {
                tssNativeBridge.outgoingMessages.collect { message ->
                    val payload = Base64.decode(message.payload, Base64.NO_WRAP)
                    grpcClient.routeMessage(
                        sessionId = sessionId,
                        fromParty = partyId,
                        toParties = message.toParties ?: emptyList(),
                        roundNumber = 0,
                        messageType = if (message.isBroadcast) "broadcast" else "p2p",
                        payload = payload
                    )
                }
            }

            // Collect incoming messages from gRPC and send to TSS
            launch {
                grpcClient.subscribeMessages(sessionId, partyId).collect { message ->
                    // Find party index from party ID
                    val session = _currentSession.value
                    val fromPartyIndex = session?.participants?.find { it.partyId == message.fromParty }?.partyIndex
                        ?: return@collect

                    tssNativeBridge.sendIncomingMessage(
                        fromPartyIndex = fromPartyIndex,
                        isBroadcast = message.isBroadcast,
                        payload = message.payload
                    )
                }
            }
        }
    }

    /**
     * Start keygen as initiator (called when session_started event is received)
     *
     * IMPORTANT: Before starting keygen, we fetch the complete participant list from
     * the backend. This is critical because the local session only contains ourselves
     * as a participant initially. Without the full list, TSS keygen will fail.
     *
     * This matches Electron's behavior in checkAndTriggerKeygen() which calls
     * getSessionStatus() to get all participants before starting keygen.
     */
    suspend fun startKeygenAsInitiator(
        sessionId: String,
        thresholdT: Int,
        thresholdN: Int,
        password: String
    ): Result<ShareRecord> = coroutineScope {
        try {
            val session = _currentSession.value
            if (session == null || session.sessionId != sessionId) {
                return@coroutineScope Result.failure(Exception("No active session"))
            }

            android.util.Log.d("TssRepository", "Starting keygen as initiator: sessionId=$sessionId")

            // CRITICAL: Fetch complete participant list from backend before starting keygen
            // This matches Electron's checkAndTriggerKeygen() behavior
            val statusResult = getSessionStatus(sessionId)
            if (statusResult.isFailure) {
                android.util.Log.e("TssRepository", "Failed to get session status: ${statusResult.exceptionOrNull()?.message}")
                return@coroutineScope Result.failure(statusResult.exceptionOrNull()!!)
            }

            val sessionStatus = statusResult.getOrThrow()
            android.util.Log.d("TssRepository", "Got session status: ${sessionStatus.participants.size} participants")

            // Build complete participant list from backend response
            val updatedParticipants = mutableListOf<Participant>()
            for (p in sessionStatus.participants) {
                val name = if (p.partyId == partyId) {
                    session.participants.firstOrNull()?.name ?: "我"
                } else {
                    "参与方 ${p.partyIndex + 1}"
                }
                updatedParticipants.add(Participant(p.partyId, p.partyIndex, name))
            }

            // Find my party index from the updated list
            val myInfo = updatedParticipants.find { it.partyId == partyId }
            val myPartyIndex = myInfo?.partyIndex ?: session.participants.firstOrNull()?.partyIndex ?: 0

            android.util.Log.d("TssRepository", "Updated participants: ${updatedParticipants.map { "${it.partyId.take(8)}:${it.partyIndex}" }}")
            android.util.Log.d("TssRepository", "My party index: $myPartyIndex")

            // Update the session with complete participant list
            _currentSession.value = session.copy(
                participants = updatedParticipants,
                thresholdT = sessionStatus.thresholdT,
                thresholdN = sessionStatus.thresholdN
            )

            // Use thresholds from backend (they may be more accurate)
            val actualThresholdT = sessionStatus.thresholdT
            val actualThresholdN = sessionStatus.thresholdN

            // Start TSS keygen with complete participant list
            val startResult = tssNativeBridge.startKeygen(
                sessionId = sessionId,
                partyId = partyId,
                partyIndex = myPartyIndex,
                thresholdT = actualThresholdT,
                thresholdN = actualThresholdN,
                participants = updatedParticipants,
                password = password
            )

            if (startResult.isFailure) {
                return@coroutineScope Result.failure(startResult.exceptionOrNull()!!)
            }

            // Start collecting progress from native bridge
            startProgressCollection()

            _sessionStatus.value = SessionStatus.IN_PROGRESS

            // Mark ready
            grpcClient.markPartyReady(sessionId, partyId)

            // Wait for keygen result
            val keygenResult = tssNativeBridge.waitForKeygenResult(password)
            if (keygenResult.isFailure) {
                stopProgressCollection()
                _sessionStatus.value = SessionStatus.FAILED
                return@coroutineScope Result.failure(keygenResult.exceptionOrNull()!!)
            }

            val result = keygenResult.getOrThrow()

            // Derive address from public key
            val publicKeyBytes = Base64.decode(result.publicKey, Base64.NO_WRAP)
            val address = AddressUtils.deriveEvmAddress(publicKeyBytes)

            // Save share record (use actual thresholds from backend)
            val shareEntity = ShareRecordEntity(
                sessionId = sessionId,
                publicKey = result.publicKey,
                encryptedShare = result.encryptedShare,
                thresholdT = actualThresholdT,
                thresholdN = actualThresholdN,
                partyIndex = myPartyIndex,
                address = address
            )
            val id = shareRecordDao.insertShare(shareEntity)

            // Report completion
            grpcClient.reportCompletion(sessionId, partyId, publicKeyBytes)

            stopProgressCollection()
            _sessionStatus.value = SessionStatus.COMPLETED
            pendingSessionId = null  // Clear pending session ID on completion
            sessionEventJob?.cancel()

            Result.success(shareEntity.copy(id = id).toShareRecord())

        } catch (e: Exception) {
            android.util.Log.e("TssRepository", "Start keygen as initiator failed", e)
            stopProgressCollection()
            _sessionStatus.value = SessionStatus.FAILED
            pendingSessionId = null  // Clear pending session ID on failure
            Result.failure(e)
        }
    }

    /**
     * Cancel current session
     */
    fun cancelSession() {
        tssNativeBridge.cancelSession()
        messageCollectionJob?.cancel()
        sessionEventJob?.cancel()
        stopSessionStatusPolling()  // Stop polling when session is cancelled
        _currentSession.value = null
        _sessionStatus.value = SessionStatus.WAITING

        // Clear pending session ID (race condition prevention)
        pendingSessionId = null

        // Clear reconnection recovery params
        currentMessageRoutingSessionId = null
        currentMessageRoutingPartyIndex = null
    }

    /**
     * Reset session status to WAITING
     * Called when navigating away from session screens to ensure fresh state
     */
    fun resetSessionStatus() {
        android.util.Log.d("TssRepository", "resetSessionStatus called - clearing session state")
        _sessionStatus.value = SessionStatus.WAITING
        stopSessionStatusPolling()  // Stop polling when resetting session state

        // CRITICAL: Clear pending session ID to avoid stale session matching
        // Without this, events from new sessions would be ignored because
        // pendingSessionId still holds the old session ID
        pendingSessionId = null
        _currentSession.value = null

        android.util.Log.d("TssRepository", "Session state cleared: pendingSessionId=null, currentSession=null")
    }

    /**
     * Delete a share record
     */
    suspend fun deleteShare(id: Long) {
        shareRecordDao.deleteShareById(id)
    }

    /**
     * Export a share as backup JSON string
     * @param shareId The ID of the share to export
     * @return Result containing the backup JSON string
     */
    suspend fun exportShareBackup(shareId: Long): Result<String> {
        return withContext(Dispatchers.IO) {
            try {
                val share = shareRecordDao.getShareById(shareId)
                    ?: return@withContext Result.failure(Exception("钱包不存在"))

                val backup = ShareBackup.fromShareRecord(share.toShareRecord())
                val json = com.google.gson.Gson().toJson(backup)

                android.util.Log.d("TssRepository", "Exported share backup for address: ${share.address}")
                Result.success(json)
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Failed to export share backup", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Import a share from backup JSON string
     * @param backupJson The backup JSON string to import
     * @return Result containing the imported ShareRecord
     */
    suspend fun importShareBackup(backupJson: String): Result<ShareRecord> {
        return withContext(Dispatchers.IO) {
            try {
                val backup = com.google.gson.Gson().fromJson(backupJson, ShareBackup::class.java)
                    ?: return@withContext Result.failure(Exception("无效的备份文件格式"))

                // Check if wallet already exists
                val existingShare = shareRecordDao.getShareByAddress(backup.address)
                if (existingShare != null) {
                    return@withContext Result.failure(Exception("此钱包已存在 (地址: ${backup.address.take(10)}...)"))
                }

                // Convert to entity and save
                val shareRecord = backup.toShareRecord()
                val entity = ShareRecordEntity(
                    sessionId = shareRecord.sessionId,
                    publicKey = shareRecord.publicKey,
                    encryptedShare = shareRecord.encryptedShare,
                    thresholdT = shareRecord.thresholdT,
                    thresholdN = shareRecord.thresholdN,
                    partyIndex = shareRecord.partyIndex,
                    address = shareRecord.address,
                    createdAt = shareRecord.createdAt
                )

                val newId = shareRecordDao.insertShare(entity)
                val savedShare = shareRecord.copy(id = newId)

                android.util.Log.d("TssRepository", "Imported share backup for address: ${backup.address}")
                Result.success(savedShare)
            } catch (e: com.google.gson.JsonSyntaxException) {
                android.util.Log.e("TssRepository", "Invalid JSON format in backup", e)
                Result.failure(Exception("备份文件格式错误"))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Failed to import share backup", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Get balance for an address using eth_getBalance RPC
     */
    suspend fun getBalance(address: String, rpcUrl: String): Result<String> {
        return withContext(Dispatchers.IO) {
            try {
                val client = okhttp3.OkHttpClient()
                val jsonMediaType = "application/json; charset=utf-8".toMediaType()
                val requestBody = """
                    {
                        "jsonrpc": "2.0",
                        "method": "eth_getBalance",
                        "params": ["$address", "latest"],
                        "id": 1
                    }
                """.trimIndent()

                val request = okhttp3.Request.Builder()
                    .url(rpcUrl)
                    .post(requestBody.toRequestBody(jsonMediaType))
                    .build()

                val response = client.newCall(request).execute()
                val responseBody = response.body?.string() ?: return@withContext Result.failure(Exception("Empty response"))

                // Parse JSON response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                if (json.has("error")) {
                    return@withContext Result.failure(Exception(json.get("error").asJsonObject.get("message").asString))
                }

                val hexBalance = json.get("result").asString
                // Convert hex to decimal (wei) then to KAVA (18 decimals)
                val weiBalance = java.math.BigInteger(hexBalance.removePrefix("0x"), 16)
                val kavaBalance = java.math.BigDecimal(weiBalance).divide(java.math.BigDecimal("1000000000000000000"), 6, java.math.RoundingMode.DOWN)

                Result.success(kavaBalance.toPlainString())
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    /**
     * Get Green Points (绿积分/dUSDT) token balance for an address
     * Uses eth_call to call balanceOf(address) on the ERC-20 contract
     */
    suspend fun getGreenPointsBalance(address: String, rpcUrl: String): Result<String> {
        return withContext(Dispatchers.IO) {
            try {
                val client = okhttp3.OkHttpClient()
                val jsonMediaType = "application/json; charset=utf-8".toMediaType()

                // Encode balanceOf(address) call data
                // Function selector: 0x70a08231
                // Address parameter: padded to 32 bytes
                val paddedAddress = address.removePrefix("0x").lowercase().padStart(64, '0')
                val callData = "${GreenPointsToken.BALANCE_OF_SELECTOR}$paddedAddress"

                val requestBody = """
                    {
                        "jsonrpc": "2.0",
                        "method": "eth_call",
                        "params": [{
                            "to": "${GreenPointsToken.CONTRACT_ADDRESS}",
                            "data": "$callData"
                        }, "latest"],
                        "id": 1
                    }
                """.trimIndent()

                val request = okhttp3.Request.Builder()
                    .url(rpcUrl)
                    .post(requestBody.toRequestBody(jsonMediaType))
                    .build()

                val response = client.newCall(request).execute()
                val responseBody = response.body?.string() ?: return@withContext Result.failure(Exception("Empty response"))

                // Parse JSON response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                if (json.has("error")) {
                    return@withContext Result.failure(Exception(json.get("error").asJsonObject.get("message").asString))
                }

                val hexBalance = json.get("result").asString
                // Convert hex to decimal, then apply 6 decimals (dUSDT uses 6 decimals like USDT)
                val rawBalance = java.math.BigInteger(hexBalance.removePrefix("0x"), 16)
                val tokenBalance = java.math.BigDecimal(rawBalance).divide(
                    java.math.BigDecimal("1000000"),  // 10^6 for 6 decimals
                    6,
                    java.math.RoundingMode.DOWN
                )

                Result.success(tokenBalance.toPlainString())
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "Failed to get Green Points balance: ${e.message}")
                Result.failure(e)
            }
        }
    }

    /**
     * Get both KAVA and Green Points balances for an address
     */
    suspend fun getWalletBalance(address: String, rpcUrl: String): Result<WalletBalance> {
        return withContext(Dispatchers.IO) {
            try {
                // Fetch both balances in parallel
                val kavaDeferred = async { getBalance(address, rpcUrl) }
                val greenPointsDeferred = async { getGreenPointsBalance(address, rpcUrl) }

                val kavaResult = kavaDeferred.await()
                val greenPointsResult = greenPointsDeferred.await()

                val kavaBalance = kavaResult.getOrDefault("0")
                val greenPointsBalance = greenPointsResult.getOrDefault("0")

                Result.success(WalletBalance(
                    address = address,
                    kavaBalance = kavaBalance,
                    greenPointsBalance = greenPointsBalance
                ))
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    // ========== Transfer / Sign Session Methods ==========

    /**
     * Prepare a transaction for signing
     */
    suspend fun prepareTransaction(
        from: String,
        to: String,
        amount: String,
        rpcUrl: String,
        chainId: Int = TransactionUtils.KAVA_TESTNET_CHAIN_ID,
        tokenType: TokenType = TokenType.KAVA
    ): Result<TransactionUtils.PreparedTransaction> {
        return TransactionUtils.prepareTransaction(
            TransactionUtils.TransactionParams(
                from = from,
                to = to,
                amount = amount,
                rpcUrl = rpcUrl,
                chainId = chainId,
                tokenType = tokenType
            )
        )
    }

    /**
     * Create a sign session for a transaction (as initiator)
     * Matches Electron's cosign:createSession flow exactly:
     * 1. Get keygen session status to get all participants with party_index
     * 2. Filter out co-managed-party-* (server backup parties)
     * 3. Create sign session with filtered signing parties
     * 4. Auto-join the session via gRPC
     * 5. Subscribe to message stream (prepareForSign)
     * 6. Check if session already in_progress and trigger sign immediately
     */
    suspend fun createSignSession(
        shareId: Long,
        messageHash: String,
        password: String,
        initiatorName: String
    ): Result<SignSessionResult> {
        return withContext(Dispatchers.IO) {
            try {
                // CRITICAL: Ensure session event subscription is active BEFORE creating sign session
                // This prevents race condition where session_started event arrives before subscription is ready
                android.util.Log.d("TssRepository", "[CO-SIGN] Ensuring session event subscription is active before creating sign session")
                ensureSessionEventSubscriptionActive()

                // Get share record
                val shareEntity = shareRecordDao.getShareById(shareId)
                    ?: return@withContext Result.failure(Exception("Share not found"))

                android.util.Log.d("TssRepository", "[CO-SIGN] Creating sign session for share: ${shareEntity.sessionId}")

                // Step 1: Get keygen session status to get all participants with party_index
                // This matches Electron's: accountClient.getSessionStatus(share.session_id)
                val keygenStatusResult = getSessionStatus(shareEntity.sessionId)
                if (keygenStatusResult.isFailure) {
                    return@withContext Result.failure(Exception("无法获取 keygen 会话的参与者信息: ${keygenStatusResult.exceptionOrNull()?.message}"))
                }
                val keygenStatus = keygenStatusResult.getOrThrow()

                if (keygenStatus.participants.isEmpty()) {
                    return@withContext Result.failure(Exception("无法获取 keygen 会话的参与者信息"))
                }

                // Step 2: Filter out co-managed-party-* (server backup parties)
                // Only user-held signing parties should participate in signing
                // This matches Electron's: filter(p => !p.party_id.startsWith('co-managed-party-'))
                val signingParties = keygenStatus.participants
                    .filter { !it.partyId.startsWith("co-managed-party-") }
                    .map { Pair(it.partyId, it.partyIndex) }

                android.util.Log.d("TssRepository", "[CO-SIGN] Signing parties (excluding co-managed-party): ${signingParties.size} of ${keygenStatus.participants.size}")
                signingParties.forEach { (id, index) ->
                    android.util.Log.d("TssRepository", "[CO-SIGN]   party_id=${id.take(16)}, party_index=$index")
                }

                if (signingParties.size < shareEntity.thresholdT) {
                    return@withContext Result.failure(Exception(
                        "签名参与方不足: 需要 ${shareEntity.thresholdT} 个，但只有 ${signingParties.size} 个用户方"
                    ))
                }

                val jsonMediaType = "application/json; charset=utf-8".toMediaType()

                // Build parties array with all signing parties (not just initiator)
                val partiesArray = com.google.gson.JsonArray().apply {
                    signingParties.forEach { (partyIdStr, partyIndex) ->
                        add(com.google.gson.JsonObject().apply {
                            addProperty("party_id", partyIdStr)
                            addProperty("party_index", partyIndex)
                        })
                    }
                }

                // Build request body matching account-service API
                // Note: Android ShareRecordEntity doesn't store wallet_name, use address-based name
                val walletName = "Wallet ${shareEntity.address.take(8)}...${shareEntity.address.takeLast(4)}"
                // Remove 0x prefix from messageHash - backend expects pure hex
                val cleanMessageHash = if (messageHash.startsWith("0x") || messageHash.startsWith("0X")) {
                    messageHash.substring(2)
                } else {
                    messageHash
                }
                val requestBody = com.google.gson.JsonObject().apply {
                    addProperty("keygen_session_id", shareEntity.sessionId)
                    addProperty("wallet_name", walletName)
                    addProperty("message_hash", cleanMessageHash)
                    add("parties", partiesArray)
                    addProperty("threshold_t", shareEntity.thresholdT)
                    addProperty("initiator_name", initiatorName)
                }.toString()

                val request = okhttp3.Request.Builder()
                    .url("$accountServiceUrl/api/v1/co-managed/sign")
                    .post(requestBody.toRequestBody(jsonMediaType))
                    .header("Content-Type", "application/json")
                    .build()

                android.util.Log.d("TssRepository", "[CO-SIGN] Creating sign session: $requestBody")

                val response = httpClient.newCall(request).execute()
                val responseBody = response.body?.string()
                    ?: return@withContext Result.failure(Exception("空响应"))

                android.util.Log.d("TssRepository", "[CO-SIGN] Create sign session response: $responseBody")

                if (!response.isSuccessful) {
                    val errorJson = try {
                        com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                    } catch (e: Exception) { null }
                    val errorMsg = errorJson?.get("message")?.asString
                        ?: errorJson?.get("error")?.asString
                        ?: "HTTP ${response.code}"
                    return@withContext Result.failure(Exception(errorMsg))
                }

                // Parse response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                val sessionId = json.get("session_id").asString
                val inviteCode = json.get("invite_code").asString
                val thresholdT = json.get("threshold_t")?.asInt ?: shareEntity.thresholdT

                // Get join token - support both new format (join_tokens map) and old format (join_token string)
                val joinToken = if (json.has("join_tokens") && json.get("join_tokens").isJsonObject) {
                    val joinTokens = json.getAsJsonObject("join_tokens")
                    joinTokens.get(partyId)?.asString ?: joinTokens.get("*")?.asString
                } else {
                    json.get("join_token")?.asString
                }

                // Build participants list from signingParties
                val participants = signingParties.map { (pId, pIndex) ->
                    Participant(
                        partyId = pId,
                        partyIndex = pIndex,
                        name = if (pId == partyId) initiatorName else "参与方 ${pIndex + 1}"
                    )
                }

                // CRITICAL: Set pendingSessionId BEFORE any async operations to avoid race condition
                // This ensures session events can be matched even if they arrive before other state is set
                pendingSessionId = sessionId
                android.util.Log.d("TssRepository", "[CO-SIGN] Set pendingSessionId=$sessionId for event matching (sign initiator)")

                // Update session state with complete participant list
                // Use original keygen thresholdN (matching Electron's share.threshold_n)
                val session = TssSession(
                    sessionId = sessionId,
                    sessionType = SessionType.SIGN,
                    thresholdT = thresholdT,
                    thresholdN = shareEntity.thresholdN, // Use original keygen N (matching Electron)
                    participants = participants,
                    status = SessionStatus.WAITING,
                    inviteCode = inviteCode,
                    messageHash = messageHash
                )
                _currentSession.value = session
                _sessionStatus.value = SessionStatus.WAITING

                // Step 4: Initiator auto-join via gRPC (matching Electron behavior)
                var sessionAlreadyInProgress = false
                if (joinToken != null) {
                    android.util.Log.d("TssRepository", "[CO-SIGN] Initiator auto-joining session...")
                    val myPartyIndex = signingParties.find { it.first == partyId }?.second ?: shareEntity.partyIndex

                    val joinResult = grpcClient.joinSession(sessionId, partyId, joinToken)
                    if (joinResult.isSuccess) {
                        val joinData = joinResult.getOrThrow()
                        android.util.Log.d("TssRepository", "[CO-SIGN] Initiator joined: partyIndex=${joinData.partyIndex}, status=${joinData.sessionStatus}")

                        // Step 5: Start message routing (prepareForSign) BEFORE sign starts
                        startMessageRouting(sessionId, myPartyIndex)

                        // CRITICAL: Ensure session event subscription is active for sign initiator
                        // The event stream may have silently disconnected
                        ensureSessionEventSubscriptionActive()

                        // Step 6: Check if session already in_progress
                        if (joinData.sessionStatus == "in_progress") {
                            android.util.Log.d("TssRepository", "[CO-SIGN] Session already in_progress, will trigger sign immediately")
                            sessionAlreadyInProgress = true
                        }
                    } else {
                        android.util.Log.w("TssRepository", "[CO-SIGN] Initiator failed to join: ${joinResult.exceptionOrNull()?.message}")
                    }
                } else {
                    android.util.Log.w("TssRepository", "[CO-SIGN] No join token found for initiator")
                }

                // If session is already in_progress, notify callback immediately
                if (sessionAlreadyInProgress) {
                    val selectedParties = signingParties.map { it.first }
                    val eventData = SessionEventData(
                        eventId = "immediate_sign_start",
                        eventType = "session_started",
                        sessionId = sessionId,
                        thresholdN = shareEntity.thresholdN, // Use original keygen N (matching Electron)
                        thresholdT = thresholdT,
                        selectedParties = selectedParties,
                        joinTokens = emptyMap(),
                        messageHash = messageHash
                    )
                    sessionEventCallback?.invoke(eventData)
                }

                Result.success(SignSessionResult(
                    sessionId = sessionId,
                    inviteCode = inviteCode,
                    sessionAlreadyInProgress = sessionAlreadyInProgress
                ))
            } catch (e: Exception) {
                android.util.Log.e("TssRepository", "[CO-SIGN] Create sign session failed", e)
                Result.failure(e)
            }
        }
    }

    /**
     * Start the signing process after all parties have joined
     * Note: Message routing should already be started in createSignSession (after auto-join)
     */
    suspend fun startSigning(
        sessionId: String,
        shareId: Long,
        password: String
    ): Result<Unit> {
        return withContext(Dispatchers.IO) {
            try {
                val session = _currentSession.value
                    ?: return@withContext Result.failure(Exception("No active session"))

                val shareEntity = shareRecordDao.getShareById(shareId)
                    ?: return@withContext Result.failure(Exception("Share not found"))

                android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: participants=${session.participants.size}")
                android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: sessionId=$sessionId, partyId=$partyId, partyIndex=${shareEntity.partyIndex}")
                android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: thresholdT=${session.thresholdT}, thresholdN=${shareEntity.thresholdN}")
                android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: messageHash=${session.messageHash?.take(20)}...")
                session.participants.forEachIndexed { idx, p ->
                    android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: participant[$idx] = ${p.partyId.take(12)}..., index=${p.partyIndex}")
                }

                // Start TSS sign
                // Remove 0x prefix from messageHash - TSS library expects pure hex
                val rawMessageHash = session.messageHash ?: ""
                val cleanMessageHash = if (rawMessageHash.startsWith("0x") || rawMessageHash.startsWith("0X")) {
                    rawMessageHash.substring(2)
                } else {
                    rawMessageHash
                }
                android.util.Log.d("TssRepository", "[CO-SIGN] Calling tssNativeBridge.startSign with cleanMessageHash=${cleanMessageHash.take(20)}...")
                val startResult = tssNativeBridge.startSign(
                    sessionId = sessionId,
                    partyId = partyId,
                    partyIndex = shareEntity.partyIndex,
                    thresholdT = session.thresholdT,
                    thresholdN = shareEntity.thresholdN,
                    participants = session.participants,
                    messageHash = cleanMessageHash,
                    shareData = shareEntity.encryptedShare,
                    password = password
                )
                android.util.Log.d("TssRepository", "[CO-SIGN] tssNativeBridge.startSign returned: isSuccess=${startResult.isSuccess}")

                if (startResult.isFailure) {
                    android.util.Log.e("TssRepository", "[CO-SIGN] startSign FAILED: ${startResult.exceptionOrNull()?.message}")
                    return@withContext Result.failure(startResult.exceptionOrNull()!!)
                }
                android.util.Log.d("TssRepository", "[CO-SIGN] startSign succeeded, starting progress collection...")

                // Start collecting progress from native bridge
                startProgressCollection()

                _sessionStatus.value = SessionStatus.IN_PROGRESS

                // Note: Message routing is already started in createSignSession after auto-join
                // Only start if not already running (for backward compatibility with old flow)
                if (messageCollectionJob == null || messageCollectionJob?.isActive != true) {
                    startMessageRouting(sessionId, shareEntity.partyIndex)
                }

                // Mark ready
                grpcClient.markPartyReady(sessionId, partyId)

                Result.success(Unit)
            } catch (e: Exception) {
                stopProgressCollection()
                _sessionStatus.value = SessionStatus.FAILED
                Result.failure(e)
            }
        }
    }

    /**
     * Wait for signing to complete and get signature
     */
    suspend fun waitForSignature(): Result<SignResult> {
        return withContext(Dispatchers.IO) {
            try {
                val signResult = tssNativeBridge.waitForSignResult()
                if (signResult.isFailure) {
                    stopProgressCollection()
                    _sessionStatus.value = SessionStatus.FAILED
                    return@withContext Result.failure(signResult.exceptionOrNull()!!)
                }

                val result = signResult.getOrThrow()

                // Report completion
                val signatureBytes = Base64.decode(result.signature, Base64.NO_WRAP)
                val session = _currentSession.value
                if (session != null) {
                    grpcClient.reportCompletion(session.sessionId, partyId, signature = signatureBytes)
                }

                stopProgressCollection()
                _sessionStatus.value = SessionStatus.COMPLETED
                messageCollectionJob?.cancel()

                Result.success(result)
            } catch (e: Exception) {
                _sessionStatus.value = SessionStatus.FAILED
                Result.failure(e)
            }
        }
    }

    /**
     * Finalize and broadcast a signed transaction
     */
    suspend fun broadcastTransaction(
        preparedTx: TransactionUtils.PreparedTransaction,
        signature: String,
        rpcUrl: String
    ): Result<String> {
        return withContext(Dispatchers.IO) {
            try {
                android.util.Log.d("TssRepository", "[BROADCAST] Input signature: ${signature.take(40)}...")
                android.util.Log.d("TssRepository", "[BROADCAST] Signature length: ${signature.length}")

                // Signature from TSS is Base64 encoded, need to decode and convert to hex
                // Format after decode: r (32 bytes) + s (32 bytes) + v (1 byte) = 65 bytes
                val sigBytes = try {
                    android.util.Base64.decode(signature, android.util.Base64.NO_WRAP)
                } catch (e: Exception) {
                    // If not Base64, try to parse as hex directly
                    android.util.Log.d("TssRepository", "[BROADCAST] Not Base64, trying hex...")
                    signature.removePrefix("0x").hexToByteArray()
                }

                android.util.Log.d("TssRepository", "[BROADCAST] Decoded signature bytes: ${sigBytes.size}")

                if (sigBytes.size != 65) {
                    return@withContext Result.failure(Exception("Invalid signature length: ${sigBytes.size} bytes, expected 65"))
                }

                // Extract r, s, v from decoded bytes
                val rBytes = sigBytes.copyOfRange(0, 32)
                val sBytes = sigBytes.copyOfRange(32, 64)
                val vByte = sigBytes[64].toInt() and 0xFF

                // Convert to hex for logging
                val rHex = rBytes.joinToString("") { "%02x".format(it) }
                val sHex = sBytes.joinToString("") { "%02x".format(it) }

                android.util.Log.d("TssRepository", "[BROADCAST] r: ${rHex.take(16)}...")
                android.util.Log.d("TssRepository", "[BROADCAST] s: ${sHex.take(16)}...")
                android.util.Log.d("TssRepository", "[BROADCAST] v: $vByte")

                val recoveryId = if (vByte >= 27) vByte - 27 else vByte

                // Use byte arrays directly for finalizeTransaction
                val r = rBytes
                val s = sBytes

                // Finalize transaction with signature
                val signedTx = TransactionUtils.finalizeTransaction(
                    preparedTx = preparedTx,
                    r = r,
                    s = s,
                    recoveryId = recoveryId
                )

                // Broadcast to network
                TransactionUtils.broadcastTransaction(signedTx, rpcUrl)
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    /**
     * Get transaction receipt
     */
    suspend fun getTransactionReceipt(
        txHash: String,
        rpcUrl: String
    ): Result<TransactionUtils.TransactionReceipt?> {
        return TransactionUtils.getTransactionReceipt(txHash, rpcUrl)
    }

    // ========== Connection Test Methods ==========

    /**
     * Test Message Router connection
     * Returns success/failure with latency
     */
    suspend fun testMessageRouter(serverUrl: String): Result<Long> {
        return withContext(Dispatchers.IO) {
            try {
                val startTime = System.currentTimeMillis()

                val parts = serverUrl.split(":")
                val host = parts[0]
                val port = parts.getOrNull(1)?.toIntOrNull() ?: 443

                // Try to connect
                grpcClient.connect(host, port)
                val testPartyId = "test-${UUID.randomUUID()}"
                grpcClient.registerParty(testPartyId, "test", "1.0.0")

                val latency = System.currentTimeMillis() - startTime
                Result.success(latency)
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    /**
     * Test Account Service connection
     * Makes a health check or simple GET request
     */
    suspend fun testAccountService(serviceUrl: String): Result<Long> {
        return withContext(Dispatchers.IO) {
            try {
                val startTime = System.currentTimeMillis()

                val client = okhttp3.OkHttpClient.Builder()
                    .connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
                    .readTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
                    .build()

                // Try health endpoint or root
                val healthUrl = if (serviceUrl.endsWith("/")) {
                    "${serviceUrl}health"
                } else {
                    "$serviceUrl/health"
                }

                val request = okhttp3.Request.Builder()
                    .url(healthUrl)
                    .get()
                    .build()

                val response = client.newCall(request).execute()
                val latency = System.currentTimeMillis() - startTime

                // Accept 200, 404 (endpoint may not exist but server is up)
                if (response.isSuccessful || response.code == 404) {
                    Result.success(latency)
                } else {
                    Result.failure(Exception("HTTP ${response.code}"))
                }
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    /**
     * Test Kava RPC connection
     * Makes an eth_chainId RPC call
     */
    suspend fun testKavaApi(rpcUrl: String): Result<Long> {
        return withContext(Dispatchers.IO) {
            try {
                val startTime = System.currentTimeMillis()

                val client = okhttp3.OkHttpClient.Builder()
                    .connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
                    .readTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
                    .build()

                val jsonMediaType = "application/json; charset=utf-8".toMediaType()
                val requestBody = """
                    {
                        "jsonrpc": "2.0",
                        "method": "eth_chainId",
                        "params": [],
                        "id": 1
                    }
                """.trimIndent()

                val request = okhttp3.Request.Builder()
                    .url(rpcUrl)
                    .post(requestBody.toRequestBody(jsonMediaType))
                    .build()

                val response = client.newCall(request).execute()
                val latency = System.currentTimeMillis() - startTime

                if (!response.isSuccessful) {
                    return@withContext Result.failure(Exception("HTTP ${response.code}"))
                }

                val responseBody = response.body?.string()
                    ?: return@withContext Result.failure(Exception("空响应"))

                // Parse JSON to verify it's a valid JSON-RPC response
                val json = com.google.gson.JsonParser.parseString(responseBody).asJsonObject
                if (json.has("error")) {
                    return@withContext Result.failure(
                        Exception(json.get("error").asJsonObject.get("message").asString)
                    )
                }

                Result.success(latency)
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    /**
     * Get a share by ID
     */
    suspend fun getShareById(shareId: Long): ShareRecord? {
        return shareRecordDao.getShareById(shareId)?.toShareRecord()
    }

    private fun String.hexToByteArray(): ByteArray {
        val len = this.length
        val data = ByteArray(len / 2)
        var i = 0
        while (i < len) {
            data[i / 2] = ((Character.digit(this[i], 16) shl 4) + Character.digit(this[i + 1], 16)).toByte()
            i += 2
        }
        return data
    }
}

/**
 * Result of creating a keygen session
 */
data class CreateKeygenSessionResult(
    val sessionId: String,
    val inviteCode: String,
    val partyIndex: Int,
    val walletName: String,
    val thresholdT: Int,
    val thresholdN: Int
)

/**
 * Result of creating a sign session
 */
data class SignSessionResult(
    val sessionId: String,
    val inviteCode: String,
    val sessionAlreadyInProgress: Boolean = false
)

/**
 * Result of validating an invite code
 */
data class ValidateInviteCodeResult(
    val sessionInfo: SessionInfoResponse,
    val joinToken: String
)

/**
 * Session info from validateInviteCode API
 */
data class SessionInfoResponse(
    val sessionId: String,
    val walletName: String,
    val thresholdT: Int,
    val thresholdN: Int,
    val initiator: String,
    val currentParticipants: Int,
    val totalParticipants: Int
)

/**
 * Result of validating a sign session invite code
 */
data class ValidateSignInviteCodeResult(
    val signSessionInfo: SignSessionInfoResponse,
    val joinToken: String
)

/**
 * Sign session info from validateSignInviteCode API
 */
data class SignSessionInfoResponse(
    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
)

/**
 * Data returned from joinSessionViaApi
 */
data class ApiJoinSessionData(
    val sessionId: String,
    val partyIndex: Int,
    val thresholdT: Int,
    val thresholdN: Int,
    val joinToken: String
)

/**
 * Data returned from joinSignSessionViaApi
 */
data class ApiJoinSignSessionData(
    val sessionId: String,
    val partyIndex: Int,
    val thresholdT: Int,
    val thresholdN: Int,
    val messageHash: String,
    val joinToken: String
)

/**
 * Result of joinKeygenSessionViaGrpc
 * Matches Electron's grpc:joinSession return value
 */
data class JoinKeygenViaGrpcResult(
    val partyIndex: Int,
    val sessionStatus: String?
)

/**
 * Result of joining sign session via gRPC
 * Matches Electron's cosign:joinSession return value
 */
data class JoinSignViaGrpcResult(
    val partyIndex: Int,
    val sessionStatus: String?,
    val shareId: Long
)

/**
 * Session status response from getSessionStatus API
 * Matches Electron's GetSessionStatusResponse
 */
data class SessionStatusResponse(
    val sessionId: String,
    val status: String,
    val thresholdT: Int,
    val thresholdN: Int,
    val completedParties: Int,
    val totalParties: Int,
    val sessionType: String,
    val participants: List<ParticipantStatusInfo>
)

/**
 * Participant status info with party_index
 */
data class ParticipantStatusInfo(
    val partyId: String,
    val partyIndex: Int,
    val status: String
)

private fun ShareRecordEntity.toShareRecord() = ShareRecord(
    id = id,
    sessionId = sessionId,
    publicKey = publicKey,
    encryptedShare = encryptedShare,
    thresholdT = thresholdT,
    thresholdN = thresholdN,
    partyIndex = partyIndex,
    address = address,
    createdAt = createdAt
)
