package de.trainingstagebuch.app.network

import android.content.Context
import de.trainingstagebuch.app.BuildConfig
import de.trainingstagebuch.app.R
import de.trainingstagebuch.app.core.SessionManager
import de.trainingstagebuch.app.core.StandardErrorMapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import java.util.concurrent.atomic.AtomicBoolean
import de.trainingstagebuch.app.utils.BackoffUtils
import de.trainingstagebuch.app.utils.SocketMessageUtils
import de.trainingstagebuch.app.utils.UiEventFlowUtils

object SocketManager {
    private val SOCKET_BASE_URL = BuildConfig.SOCKET_BASE_URL
    private const val MAX_RECONNECT_DELAY_MS = 30_000L
    private const val INITIAL_RECONNECT_DELAY_MS = 1_000L

    private val initialized = AtomicBoolean(false)
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val client = OkHttpClient()

    @Volatile
    private var authCode: String? = null

    @Volatile
    private var userId: String? = null

    @Volatile
    private var currentClubId: String? = null

    @Volatile
    private var appInForeground: Boolean = false

    @Volatile
    private var shouldMaintainConnection: Boolean = false

    @Volatile
    private var reconnectAttempts: Int = 0

    @Volatile
    private var webSocket: WebSocket? = null

    private val _events = UiEventFlowUtils.createEventFlow<SocketEvent>(extraBufferCapacity = 64)
    val events: SharedFlow<SocketEvent> = _events.asSharedFlow()

    private val _connectionState = MutableStateFlow<SocketConnectionState>(SocketConnectionState.Disconnected)
    val connectionState: StateFlow<SocketConnectionState> = _connectionState.asStateFlow()

    fun initialize(context: Context) {
        if (!initialized.compareAndSet(false, true)) return
        val sessionManager = SessionManager(context.applicationContext)
        scope.launch {
            sessionManager.session.collectLatest { session ->
                val previousClubId = currentClubId
                authCode = session.token
                userId = session.username
                currentClubId = session.currentClub
                if (session.token.isNullOrBlank() || session.username.isNullOrBlank()) {
                    shouldMaintainConnection = false
                    disconnectInternal("Session missing")
                } else if (shouldMaintainConnection && appInForeground) {
                    connectInternal()
                }
                if (webSocket != null && previousClubId != currentClubId) {
                    previousClubId?.takeIf { it.isNotBlank() }?.let { oldClub ->
                        webSocket?.send(SocketMessageUtils.buildLeaveClubMessage(oldClub))
                    }
                    currentClubId?.takeIf { it.isNotBlank() }?.let { newClub ->
                        webSocket?.send(SocketMessageUtils.buildJoinClubMessage(newClub))
                    }
                }
            }
        }
    }

    fun setAppInForeground(inForeground: Boolean) {
        appInForeground = inForeground
        if (!inForeground) {
            disconnectInternal("App background")
            return
        }
        if (shouldMaintainConnection) {
            connectInternal()
        }
    }

    fun requestConnect() {
        shouldMaintainConnection = true
        if (appInForeground) {
            connectInternal()
        }
    }

    fun requestDisconnect() {
        shouldMaintainConnection = false
        disconnectInternal("Requested")
    }

    private fun connectInternal() {
        val token = authCode
        val username = userId
        if (token.isNullOrBlank() || username.isNullOrBlank()) {
            _connectionState.value = SocketConnectionState.Failed(
                StandardErrorMapper.fromRes(R.string.error_missing_auth)
            )
            return
        }
        val existing = webSocket
        if (existing != null) {
            return
        }

        _connectionState.value = SocketConnectionState.Connecting
        val request = Request.Builder()
            .url(buildSocketUrl(token, username))
            .build()

        webSocket = client.newWebSocket(request, object : WebSocketListener() {
            override fun onOpen(webSocket: WebSocket, response: Response) {
                reconnectAttempts = 0
                _connectionState.value = SocketConnectionState.Connected(0)
                // Socket.IO namespace connect with auth payload (like Vue auth context)
                webSocket.send("40{\"authcode\":\"$token\",\"userid\":\"$username\"}")
                webSocket.send(SocketMessageUtils.buildAuthMessage(token, username))
                currentClubId?.takeIf { it.isNotBlank() }?.let {
                    webSocket.send(SocketMessageUtils.buildJoinClubMessage(it))
                }
            }

            override fun onMessage(webSocket: WebSocket, text: String) {
                // Engine.IO ping -> pong
                if (text == "2") {
                    webSocket.send("3")
                    return
                }
                val event = SocketMessageUtils.parseSocketIoEvent(text)
                if (event != null) {
                    UiEventFlowUtils.emitSafe(_events, event)
                }
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                this@SocketManager.webSocket = null
                _connectionState.value = SocketConnectionState.Failed(
                    StandardErrorMapper.map(t).message
                )
                scheduleReconnectIfNeeded()
            }

            override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
                this@SocketManager.webSocket = null
                _connectionState.value = SocketConnectionState.Disconnected
                scheduleReconnectIfNeeded()
            }
        })
    }

    private fun scheduleReconnectIfNeeded() {
        if (!shouldMaintainConnection || !appInForeground) {
            return
        }
        reconnectAttempts += 1
        val delayMs = BackoffUtils.exponentialDelayMs(
            attempt = reconnectAttempts,
            initialDelayMs = INITIAL_RECONNECT_DELAY_MS,
            maxDelayMs = MAX_RECONNECT_DELAY_MS
        )
        _connectionState.value = SocketConnectionState.Reconnecting(reconnectAttempts, delayMs)
        scope.launch {
            delay(delayMs)
            if (shouldMaintainConnection && appInForeground && webSocket == null) {
                connectInternal()
            }
        }
    }

    private fun disconnectInternal(reason: String) {
        val ws = webSocket ?: run {
            _connectionState.value = SocketConnectionState.Disconnected
            return
        }
        ws.close(1000, reason)
        webSocket = null
        _connectionState.value = SocketConnectionState.Disconnected
    }

    private fun buildSocketUrl(authCode: String, userId: String): String {
        val wsBase = SOCKET_BASE_URL
            .replaceFirst("https://", "wss://")
            .replaceFirst("http://", "ws://")
            .trimEnd('/')
        return "$wsBase/socket.io/?EIO=4&transport=websocket&authcode=$authCode&userid=$userId"
    }
}
