package com.rosetta.messenger.di

import android.content.Context
import com.rosetta.messenger.data.AccountManager
import com.rosetta.messenger.data.DecryptedAccount
import com.rosetta.messenger.data.PreferencesManager
import com.rosetta.messenger.network.DeviceEntry
import com.rosetta.messenger.network.Packet
import com.rosetta.messenger.network.PacketMessage
import com.rosetta.messenger.network.ProtocolClient
import com.rosetta.messenger.network.ProtocolRuntime
import com.rosetta.messenger.network.ProtocolRuntimePort
import com.rosetta.messenger.network.RuntimeComposition
import com.rosetta.messenger.network.SearchUser
import com.rosetta.messenger.session.SessionAction
import com.rosetta.messenger.session.IdentityStateSnapshot
import com.rosetta.messenger.session.IdentityStore
import com.rosetta.messenger.session.SessionState
import com.rosetta.messenger.session.SessionStore
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Inject
import javax.inject.Singleton
import javax.inject.Provider
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow

interface ProtocolGateway : ProtocolRuntimePort {
    val syncInProgress: StateFlow<Boolean>
    val pendingDeviceVerification: StateFlow<DeviceEntry?>
    val typingUsers: StateFlow<Set<String>>
    val typingUsersByDialogSnapshot: StateFlow<Map<String, Set<String>>>
    val ownProfileUpdated: StateFlow<Long>

    fun initialize(context: Context)
    fun initializeAccount(publicKey: String, privateKey: String)
    fun connect()
    fun authenticate(publicKey: String, privateHash: String)
    fun disconnect()
    fun getPrivateHash(): String?
    fun subscribePushTokenIfAvailable(forceToken: String? = null)
    fun enableUILogs(enabled: Boolean)
    fun clearLogs()
    fun resolveOutgoingRetry(messageId: String)
    fun getCachedUserByUsername(username: String): SearchUser?
    fun getCachedUserName(publicKey: String): String?
    fun acceptDevice(deviceId: String)
    fun declineDevice(deviceId: String)
    fun sendMessageWithRetry(packet: PacketMessage)
    fun packetFlow(packetId: Int): SharedFlow<Packet>
    fun notifyOwnProfileUpdated()
    suspend fun resolveUserName(publicKey: String, timeoutMs: Long = 3000): String?
    suspend fun searchUsers(query: String, timeoutMs: Long = 3000): List<SearchUser>
    fun setAppInForeground(foreground: Boolean)
}

interface SessionCoordinator {
    val sessionState: StateFlow<SessionState>

    fun dispatch(action: SessionAction)
    fun markLoggedOut(reason: String = "") =
        dispatch(SessionAction.LoggedOut(reason = reason))
    fun markAuthInProgress(publicKey: String? = null, reason: String = "") =
        dispatch(
            SessionAction.AuthInProgress(
                publicKey = publicKey,
                reason = reason
            )
        )
    fun markReady(account: DecryptedAccount, reason: String = "") =
        dispatch(SessionAction.Ready(account = account, reason = reason))
    fun syncFromCachedAccount(account: DecryptedAccount?) =
        dispatch(SessionAction.SyncFromCachedAccount(account = account))
    suspend fun bootstrapAuthenticatedSession(account: DecryptedAccount, reason: String)
}

interface IdentityGateway {
    val state: StateFlow<IdentityStateSnapshot>

    fun updateOwnProfile(
        publicKey: String,
        displayName: String? = null,
        username: String? = null,
        verified: Int? = null,
        resolved: Boolean = true,
        reason: String = ""
    )
}

@Singleton
/**
 * Thin infrastructure adapter for repositories.
 *
 * This bridge is intentionally excluded from business-flow hop-depth accounting and exists
 * to keep lazy runtime access (`Provider<ProtocolRuntime>`) and avoid DI cycles.
 */
class ProtocolClientImpl @Inject constructor(
    private val runtimeProvider: Provider<ProtocolRuntime>
) : ProtocolClient {
    override fun send(packet: Packet) = runtimeProvider.get().send(packet)

    override fun sendMessageWithRetry(packet: PacketMessage) =
        runtimeProvider.get().sendMessageWithRetry(packet)

    override fun addLog(message: String) = runtimeProvider.get().addLog(message)

    override fun waitPacket(packetId: Int, callback: (Packet) -> Unit) =
        runtimeProvider.get().waitPacket(packetId, callback)

    override fun unwaitPacket(packetId: Int, callback: (Packet) -> Unit) =
        runtimeProvider.get().unwaitPacket(packetId, callback)
}

@Singleton
class SessionCoordinatorImpl @Inject constructor(
    private val accountManager: AccountManager,
    private val protocolGateway: ProtocolGateway
) : SessionCoordinator {
    override val sessionState: StateFlow<SessionState> = SessionStore.state

    override fun dispatch(action: SessionAction) {
        SessionStore.dispatch(action)
    }

    override suspend fun bootstrapAuthenticatedSession(account: DecryptedAccount, reason: String) {
        dispatch(SessionAction.AuthInProgress(publicKey = account.publicKey, reason = reason))
        protocolGateway.initializeAccount(account.publicKey, account.privateKey)
        protocolGateway.connect()
        protocolGateway.authenticate(account.publicKey, account.privateKeyHash)
        protocolGateway.reconnectNowIfNeeded("session_bootstrap_$reason")
        accountManager.setCurrentAccount(account.publicKey)
        dispatch(SessionAction.Ready(account = account, reason = reason))
    }
}

@Singleton
class IdentityGatewayImpl @Inject constructor() : IdentityGateway {
    override val state: StateFlow<IdentityStateSnapshot> = IdentityStore.state

    override fun updateOwnProfile(
        publicKey: String,
        displayName: String?,
        username: String?,
        verified: Int?,
        resolved: Boolean,
        reason: String
    ) {
        IdentityStore.updateOwnProfile(
            publicKey = publicKey,
            displayName = displayName,
            username = username,
            verified = verified,
            resolved = resolved,
            reason = reason
        )
    }
}

@Module
@InstallIn(SingletonComponent::class)
object AppDataModule {
    @Provides
    @Singleton
    fun provideAccountManager(@ApplicationContext context: Context): AccountManager =
        AccountManager(context)

    @Provides
    @Singleton
    fun providePreferencesManager(@ApplicationContext context: Context): PreferencesManager =
        PreferencesManager(context)

    @Provides
    @Singleton
    fun provideRuntimeComposition(): RuntimeComposition = RuntimeComposition()

}

@Module
@InstallIn(SingletonComponent::class)
abstract class AppGatewayModule {
    @Binds
    @Singleton
    abstract fun bindProtocolGateway(runtime: ProtocolRuntime): ProtocolGateway

    @Binds
    @Singleton
    abstract fun bindSessionCoordinator(impl: SessionCoordinatorImpl): SessionCoordinator

    @Binds
    @Singleton
    abstract fun bindIdentityGateway(impl: IdentityGatewayImpl): IdentityGateway

    @Binds
    @Singleton
    abstract fun bindProtocolClient(impl: ProtocolClientImpl): ProtocolClient
}
