package com.rosetta.messenger.network

import android.content.Context
import com.rosetta.messenger.data.AccountManager
import com.rosetta.messenger.data.GroupRepository
import com.rosetta.messenger.data.MessageRepository
import com.rosetta.messenger.data.isPlaceholderAccountName
import com.rosetta.messenger.network.connection.AuthBootstrapCoordinator
import com.rosetta.messenger.network.connection.AuthRestoreService
import com.rosetta.messenger.network.connection.ProtocolAccountSessionCoordinator
import com.rosetta.messenger.network.connection.ConnectionOrchestrator
import com.rosetta.messenger.network.connection.DeviceRuntimeService
import com.rosetta.messenger.network.connection.OwnProfileSyncService
import com.rosetta.messenger.network.connection.ProtocolDebugLogService
import com.rosetta.messenger.network.connection.ProtocolLifecycleCoordinator
import com.rosetta.messenger.network.connection.ProtocolPostAuthBootstrapCoordinator
import com.rosetta.messenger.network.connection.ReadyPacketDispatchCoordinator
import com.rosetta.messenger.network.connection.RuntimeInitializationCoordinator
import com.rosetta.messenger.network.connection.RuntimeShutdownCoordinator
import com.rosetta.messenger.session.IdentityStore
import com.rosetta.messenger.utils.MessageLogger
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.*

/**
 * Singleton manager for Protocol instance
 * Ensures single connection across the app
 */
class RuntimeComposition {
    private val TAG = "ProtocolRuntime"
    private val MANUAL_SYNC_BACKTRACK_MS = 120_000L
    private val SYNC_REQUEST_TIMEOUT_MS = 12_000L
    private val MAX_DEBUG_LOGS = 600
    private val DEBUG_LOG_FLUSH_DELAY_MS = 60L
    private val HEARTBEAT_OK_LOG_MIN_INTERVAL_MS = 5_000L
    private val TYPING_INDICATOR_TIMEOUT_MS = 3_000L
    private val PROTOCOL_TRACE_FILE_NAME = "protocol_wire_log.txt"
    private val PROTOCOL_TRACE_MAX_BYTES = 2_000_000L
    private val PROTOCOL_TRACE_KEEP_BYTES = 1_200_000
    private val NETWORK_WAIT_TIMEOUT_MS = 20_000L
    private val BOOTSTRAP_OWN_PROFILE_FALLBACK_MS = 2_500L
    private val READY_PACKET_QUEUE_MAX = 500
    private val READY_PACKET_QUEUE_TTL_MS = 120_000L
    
    // Desktop parity: use the same primary WebSocket endpoint as desktop client.
    private val SERVER_ADDRESS = "wss://wss.rosetta.im"
    
    private var messageRepository: MessageRepository? = null
    private var groupRepository: GroupRepository? = null
    private var accountManager: AccountManager? = null
    private var appContext: Context? = null
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private val transportAssembly =
        RuntimeTransportAssembly(
            scope = scope,
            networkWaitTimeoutMs = NETWORK_WAIT_TIMEOUT_MS,
            serverAddress = SERVER_ADDRESS,
            getAppContext = { appContext },
            addLog = ::addLog,
            onFastReconnectRequested = ::onFastReconnectRequested
        )
    private val networkConnectivityFacade = transportAssembly.networkConnectivityFacade
    private val protocolInstanceManager = transportAssembly.protocolInstanceManager
    private val packetSubscriptionFacade = transportAssembly.packetSubscriptionFacade
    // Guard: prevent duplicate FCM token subscribe within a single session
    @Volatile
    private var lastSubscribedToken: String? = null
    private val stateAssembly =
        RuntimeStateAssembly(
            scope = scope,
            readyPacketQueueMax = READY_PACKET_QUEUE_MAX,
            readyPacketQueueTtlMs = READY_PACKET_QUEUE_TTL_MS,
            ownProfileFallbackTimeoutMs = BOOTSTRAP_OWN_PROFILE_FALLBACK_MS,
            addLog = ::addLog,
            shortKeyForLog = ::shortKeyForLog,
            sendPacketDirect = { packet -> getProtocol().sendPacket(packet) },
            onOwnProfileFallbackTimeout = ::onOwnProfileFallbackTimeoutEvent,
            clearLastSubscribedTokenValue = { lastSubscribedToken = null }
        )
    private val bootstrapCoordinator get() = stateAssembly.bootstrapCoordinator
    private val lifecycleStateMachine get() = stateAssembly.lifecycleStateMachine
    private val lifecycleStateStore get() = stateAssembly.lifecycleStateStore
    private val deviceRuntimeService =
        DeviceRuntimeService(
            getAppContext = { appContext },
            sendPacket = ::send
        )
    private val connectionOrchestrator =
        ConnectionOrchestrator(
            hasActiveInternet = networkConnectivityFacade::hasActiveInternet,
            waitForNetworkAndReconnect = networkConnectivityFacade::waitForNetworkAndReconnect,
            stopWaitingForNetwork = { reason -> networkConnectivityFacade.stopWaitingForNetwork(reason) },
            getProtocol = ::getProtocol,
            persistHandshakeCredentials = { publicKey, privateHash ->
                accountManager?.setLastLoggedPublicKey(publicKey)
                accountManager?.setLastLoggedPrivateKeyHash(privateHash)
            },
            buildHandshakeDevice = deviceRuntimeService::buildHandshakeDevice
        )
    private val ownProfileSyncService =
        OwnProfileSyncService(
            isPlaceholderAccountName = ::isPlaceholderAccountName,
            updateAccountName = { publicKey, name ->
                accountManager?.updateAccountName(publicKey, name)
            },
            updateAccountUsername = { publicKey, username ->
                accountManager?.updateAccountUsername(publicKey, username)
            }
        )
    private val authBootstrapCoordinator =
        AuthBootstrapCoordinator(
            scope = scope,
            addLog = ::addLog
        )
    private val messagingAssembly by lazy {
        RuntimeMessagingAssembly(
            tag = TAG,
            scope = scope,
            typingIndicatorTimeoutMs = TYPING_INDICATOR_TIMEOUT_MS,
            syncRequestTimeoutMs = SYNC_REQUEST_TIMEOUT_MS,
            manualSyncBacktrackMs = MANUAL_SYNC_BACKTRACK_MS,
            deviceRuntimeService = deviceRuntimeService,
            ownProfileSyncService = ownProfileSyncService,
            isAuthenticated = ::isAuthenticated,
            getProtocolPublicKey = { getProtocol().getPublicKey().orEmpty() },
            getProtocolPrivateHash = {
                try {
                    getProtocol().getPrivateHash()
                } catch (_: Exception) {
                    null
                }
            },
            getMessageRepository = { messageRepository },
            getGroupRepository = { groupRepository },
            sendSearchPacket = ::sendSearchPacketViaPacketIo,
            sendMessagePacket = ::sendMessagePacketViaPacketIo,
            sendSyncPacket = ::sendSyncPacketViaPacketIo,
            sendPacket = ::send,
            waitPacket = ::waitPacket,
            unwaitPacket = ::unwaitPacket,
            addLog = ::addLog,
            shortKeyForLog = ::shortKeyForLog,
            shortTextForLog = ::shortTextForLog,
            onSyncCompleted = ::finishSyncCycle,
            onInboundTaskQueued = ::onInboundTaskQueued,
            onInboundTaskFailure = ::markInboundProcessingFailure,
            resolveOutgoingRetry = ::resolveOutgoingRetry,
            isGroupDialogKey = ::isGroupDialogKey,
            setTransportServer = TransportManager::setTransportServer,
            onOwnProfileResolved = ::onOwnProfileResolvedEvent
        )
    }
    private val packetRouter get() = messagingAssembly.packetRouter
    private val outgoingMessagePipelineService get() = messagingAssembly.outgoingMessagePipelineService
    private val presenceTypingService get() = messagingAssembly.presenceTypingService
    private val inboundTaskQueueService get() = messagingAssembly.inboundTaskQueueService
    private val syncCoordinator get() = messagingAssembly.syncCoordinator
    private val callSignalBridge get() = messagingAssembly.callSignalBridge
    private val inboundPacketHandlerRegistrar get() = messagingAssembly.inboundPacketHandlerRegistrar

    val connectionLifecycleState: StateFlow<ConnectionLifecycleState> =
        stateAssembly.connectionLifecycleState
    private val postAuthBootstrapCoordinator =
        ProtocolPostAuthBootstrapCoordinator(
            tag = TAG,
            scope = scope,
            authBootstrapCoordinator = authBootstrapCoordinator,
            syncCoordinator = syncCoordinator,
            ownProfileSyncService = ownProfileSyncService,
            deviceRuntimeService = deviceRuntimeService,
            getMessageRepository = { messageRepository },
            getAppContext = { appContext },
            getProtocolPublicKey = { getProtocol().getPublicKey() },
            getProtocolPrivateHash = { getProtocol().getPrivateHash() },
            sendPacket = ::send,
            requestTransportServer = TransportManager::requestTransportServer,
            requestUpdateServer = { com.rosetta.messenger.update.UpdateManager.requestSduServer() },
            addLog = ::addLog,
            shortKeyForLog = { value -> shortKeyForLog(value) },
            getLastSubscribedToken = { lastSubscribedToken },
            setLastSubscribedToken = { token -> lastSubscribedToken = token }
        )
    private val lifecycleCoordinator =
        ProtocolLifecycleCoordinator(
            stateStore = lifecycleStateStore,
            syncCoordinator = syncCoordinator,
            authBootstrapCoordinator = authBootstrapCoordinator,
            addLog = ::addLog,
            shortKeyForLog = { value -> shortKeyForLog(value) },
            stopWaitingForNetwork = { reason -> networkConnectivityFacade.stopWaitingForNetwork(reason) },
            cancelAllOutgoingRetries = ::cancelAllOutgoingRetries,
            recomputeConnectionLifecycleState = stateAssembly::recomputeConnectionLifecycleState,
            onAuthenticated = { postAuthBootstrapCoordinator.runPostAuthBootstrap("state_authenticated") },
            onSyncCompletedSideEffects = postAuthBootstrapCoordinator::handleSyncCompletedSideEffects,
            updateOwnProfileResolved = { publicKey, reason ->
                IdentityStore.updateOwnProfile(
                    publicKey = publicKey,
                    resolved = true,
                    reason = reason
                )
            }
        )
    private val readyPacketDispatchCoordinator =
        ReadyPacketDispatchCoordinator(
            bootstrapCoordinator = bootstrapCoordinator,
            getConnectionLifecycleState = lifecycleStateMachine::currentState,
            resolveAccountPublicKey = {
                messageRepository?.getCurrentAccountKey()?.trim().orEmpty().ifBlank {
                    lifecycleStateMachine.bootstrapContext.accountPublicKey
                }
            },
            sendPacketDirect = { packet -> getProtocol().sendPacket(packet) },
            isAuthenticated = ::isAuthenticated,
            hasActiveInternet = networkConnectivityFacade::hasActiveInternet,
            waitForNetworkAndReconnect = networkConnectivityFacade::waitForNetworkAndReconnect,
            reconnectNowIfNeeded = { reason -> getProtocol().reconnectNowIfNeeded(reason) }
        )
    private val accountSessionCoordinator =
        ProtocolAccountSessionCoordinator(
            stateStore = lifecycleStateStore,
            syncCoordinator = syncCoordinator,
            authBootstrapCoordinator = authBootstrapCoordinator,
            presenceTypingService = presenceTypingService,
            deviceRuntimeService = deviceRuntimeService,
            getMessageRepository = { messageRepository },
            getProtocolState = { state.value },
            isProtocolAuthenticated = ::isAuthenticated,
            addLog = ::addLog,
            shortKeyForLog = { value -> shortKeyForLog(value) },
            clearReadyPacketQueue = readyPacketDispatchCoordinator::clearReadyPacketQueue,
            recomputeConnectionLifecycleState = stateAssembly::recomputeConnectionLifecycleState,
            stopWaitingForNetwork = { reason -> networkConnectivityFacade.stopWaitingForNetwork(reason) },
            disconnectProtocol = protocolInstanceManager::disconnect,
            tryRunPostAuthBootstrap = postAuthBootstrapCoordinator::runPostAuthBootstrap,
            launchVersionUpdateCheck = {
                scope.launch {
                    messageRepository?.checkAndSendVersionUpdateMessage()
                }
            }
        )
    private val initializationCoordinator =
        RuntimeInitializationCoordinator(
            ensureConnectionSupervisor = ::ensureConnectionSupervisor,
            setupPacketHandlers = ::setupPacketHandlers,
            setupStateMonitoring = ::setupStateMonitoring,
            setAppContext = { context -> appContext = context },
            hasBoundDependencies = {
                messageRepository != null && groupRepository != null && accountManager != null
            },
            addLog = ::addLog
        )
    private val authRestoreService =
        AuthRestoreService(
            getAccountManager = { accountManager },
            addLog = ::addLog,
            shortKeyForLog = ::shortKeyForLog,
            authenticate = ::authenticate
        )
    private val runtimeShutdownCoordinator =
        RuntimeShutdownCoordinator(
            stopWaitingForNetwork = { reason -> networkConnectivityFacade.stopWaitingForNetwork(reason) },
            destroyPacketSubscriptionRegistry = transportAssembly::destroyPacketSubscriptions,
            destroyProtocolInstance = protocolInstanceManager::destroy,
            clearMessageRepositoryInitialization = {
                messageRepository?.clearInitialization()
            },
            clearPresenceTyping = presenceTypingService::clear,
            clearDeviceRuntime = deviceRuntimeService::clear,
            resetSyncCoordinator = syncCoordinator::resetForDisconnect,
            resetAuthBootstrap = authBootstrapCoordinator::reset,
            cancelRuntimeScope = scope::cancel
        )
    private val routingAssembly by lazy {
        RuntimeRoutingAssembly(
            scope = scope,
            tag = TAG,
            addLog = ::addLog,
            handleInitializeAccount = accountSessionCoordinator::handleInitializeAccount,
            handleConnect = connectionOrchestrator::handleConnect,
            handleFastReconnect = connectionOrchestrator::handleFastReconnect,
            handleDisconnect = accountSessionCoordinator::handleDisconnect,
            handleAuthenticate = connectionOrchestrator::handleAuthenticate,
            handleProtocolStateChanged = lifecycleCoordinator::handleProtocolStateChanged,
            handleSendPacket = readyPacketDispatchCoordinator::handleSendPacket,
            handleSyncCompleted = lifecycleCoordinator::handleSyncCompleted,
            handleOwnProfileResolved = lifecycleCoordinator::handleOwnProfileResolved,
            handleOwnProfileFallbackTimeout = lifecycleCoordinator::handleOwnProfileFallbackTimeout
        )
    }
    private val connectionControlFacade by lazy {
        RuntimeConnectionControlFacade(
            postConnectionEvent = ::postConnectionEvent,
            initializationCoordinator = initializationCoordinator,
            syncCoordinator = syncCoordinator,
            authRestoreService = authRestoreService,
            runtimeShutdownCoordinator = runtimeShutdownCoordinator,
            protocolInstanceManager = protocolInstanceManager,
            subscribePushTokenIfAvailable = postAuthBootstrapCoordinator::subscribePushTokenIfAvailable
        )
    }
    private val directoryFacade by lazy {
        RuntimeDirectoryFacade(
            packetRouter = packetRouter,
            ownProfileSyncService = ownProfileSyncService,
            deviceRuntimeService = deviceRuntimeService,
            presenceTypingService = presenceTypingService
        )
    }
    private val packetIoFacade by lazy {
        RuntimePacketIoFacade(
            postConnectionEvent = ::postConnectionEvent,
            outgoingMessagePipelineServiceProvider = { outgoingMessagePipelineService },
            callSignalBridge = callSignalBridge,
            packetSubscriptionFacade = packetSubscriptionFacade
        )
    }

    private val debugLogService =
        ProtocolDebugLogService(
            scope = scope,
            maxDebugLogs = MAX_DEBUG_LOGS,
            debugLogFlushDelayMs = DEBUG_LOG_FLUSH_DELAY_MS,
            heartbeatOkLogMinIntervalMs = HEARTBEAT_OK_LOG_MIN_INTERVAL_MS,
            protocolTraceFileName = PROTOCOL_TRACE_FILE_NAME,
            protocolTraceMaxBytes = PROTOCOL_TRACE_MAX_BYTES,
            protocolTraceKeepBytes = PROTOCOL_TRACE_KEEP_BYTES,
            appContextProvider = { appContext }
        )
    val debugLogs: StateFlow<List<String>> = debugLogService.debugLogs
    val typingUsers: StateFlow<Set<String>> = presenceTypingService.typingUsers
    val typingUsersByDialogSnapshot: StateFlow<Map<String, Set<String>>> =
        presenceTypingService.typingUsersByDialogSnapshot

    val devices: StateFlow<List<DeviceEntry>> = deviceRuntimeService.devices
    val pendingDeviceVerification: StateFlow<DeviceEntry?> =
        deviceRuntimeService.pendingDeviceVerification

    // Сигнал обновления own profile (username/name загружены с сервера)
    val ownProfileUpdated: StateFlow<Long> = ownProfileSyncService.ownProfileUpdated
    val syncInProgress: StateFlow<Boolean> = syncCoordinator.syncInProgress

    fun connectionControlApi(): RuntimeConnectionControlFacade = connectionControlFacade

    fun directoryApi(): RuntimeDirectoryFacade = directoryFacade

    fun packetIoApi(): RuntimePacketIoFacade = packetIoFacade

    private fun sendSearchPacketViaPacketIo(packet: PacketSearch) {
        packetIoFacade.send(packet)
    }

    private fun sendMessagePacketViaPacketIo(packet: PacketMessage) {
        packetIoFacade.send(packet)
    }

    private fun sendSyncPacketViaPacketIo(packet: PacketSync) {
        packetIoFacade.send(packet)
    }

    private fun ensureConnectionSupervisor() {
        routingAssembly.start()
    }

    private fun postConnectionEvent(event: ConnectionEvent) {
        routingAssembly.post(event)
    }

    private fun onFastReconnectRequested(reason: String) {
        postConnectionEvent(ConnectionEvent.FastReconnect(reason))
    }

    private fun onOwnProfileResolvedEvent(publicKey: String) {
        postConnectionEvent(ConnectionEvent.OwnProfileResolved(publicKey))
    }

    private fun onOwnProfileFallbackTimeoutEvent(generation: Long) {
        postConnectionEvent(ConnectionEvent.OwnProfileFallbackTimeout(generation))
    }

    fun addLog(message: String) {
        debugLogService.addLog(message)
    }

    fun enableUILogs(enabled: Boolean) {
        debugLogService.enableUILogs(enabled)
        MessageLogger.setEnabled(enabled)
    }

    fun clearLogs() {
        debugLogService.clearLogs()
    }

    private fun markInboundProcessingFailure(reason: String, error: Throwable? = null) {
        syncCoordinator.markInboundProcessingFailure()
        if (error != null) {
            android.util.Log.e(TAG, reason, error)
            addLog("❌ $reason: ${error.message ?: error.javaClass.simpleName}")
        } else {
            android.util.Log.w(TAG, reason)
            addLog("⚠️ $reason")
        }
    }

    private fun onInboundTaskQueued() {
        syncCoordinator.trackInboundTaskQueued()
    }

    /**
     * Inject process-wide dependencies from DI container.
     */
    fun bindDependencies(
        messageRepository: MessageRepository,
        groupRepository: GroupRepository,
        accountManager: AccountManager
    ) {
        this.messageRepository = messageRepository
        this.groupRepository = groupRepository
        this.accountManager = accountManager
    }

    /**
     * Backward-compatible alias kept while migrating call sites.
     */
    fun bindRepositories(
        messageRepository: MessageRepository,
        groupRepository: GroupRepository
    ) {
        this.messageRepository = messageRepository
        this.groupRepository = groupRepository
    }

    private fun setupStateMonitoring() {
        scope.launch {
            state.collect { newState ->
                postConnectionEvent(ConnectionEvent.ProtocolStateChanged(newState))
            }
        }
    }

    private fun setupPacketHandlers() {
        inboundPacketHandlerRegistrar.register()
    }

    private fun isGroupDialogKey(value: String): Boolean {
        val normalized = value.trim().lowercase(Locale.ROOT)
        return normalized.startsWith("#group:") || normalized.startsWith("group:")
    }

    private fun finishSyncCycle(reason: String) {
        postConnectionEvent(ConnectionEvent.SyncCompleted(reason))
    }

    private fun getProtocol(): Protocol {
        return protocolInstanceManager.getOrCreateProtocol()
    }

    val state: StateFlow<ProtocolState>
        get() = protocolInstanceManager.state

    private fun send(packet: Packet) {
        packetIoFacade.send(packet)
    }

    private fun resolveOutgoingRetry(messageId: String) {
        packetIoFacade.resolveOutgoingRetry(messageId)
    }

    private fun cancelAllOutgoingRetries() {
        packetIoFacade.clearOutgoingRetries()
    }

    private fun authenticate(publicKey: String, privateHash: String) {
        postConnectionEvent(
            ConnectionEvent.Authenticate(publicKey = publicKey, privateHash = privateHash)
        )
    }

    private fun waitPacket(packetId: Int, callback: (Packet) -> Unit) {
        packetIoFacade.waitPacket(packetId, callback)
    }

    private fun unwaitPacket(packetId: Int, callback: (Packet) -> Unit) {
        packetIoFacade.unwaitPacket(packetId, callback)
    }

    private fun shortKeyForLog(value: String, visible: Int = 8): String {
        val trimmed = value.trim()
        if (trimmed.isBlank()) return "<empty>"
        return if (trimmed.length <= visible) trimmed else "${trimmed.take(visible)}…"
    }

    private fun shortTextForLog(value: String, limit: Int = 80): String {
        val normalized = value.replace('\n', ' ').replace('\r', ' ').trim()
        if (normalized.isBlank()) return "<empty>"
        return if (normalized.length <= limit) normalized else "${normalized.take(limit)}…"
    }
    
    private fun isAuthenticated(): Boolean = connectionControlFacade.isAuthenticated()
}
