package de.krisenvorrat.app.data.repository

import de.krisenvorrat.app.data.db.dao.MessageDao
import de.krisenvorrat.app.data.db.entity.MessageEntity
import de.krisenvorrat.app.data.sync.WebSocketClient
import de.krisenvorrat.app.data.sync.WebSocketEvent
import de.krisenvorrat.app.di.ApplicationScope
import de.krisenvorrat.app.domain.model.SettingsKeys
import de.krisenvorrat.app.domain.model.SyncError
import de.krisenvorrat.app.domain.repository.MessageRepository
import de.krisenvorrat.app.domain.repository.SettingsRepository
import de.krisenvorrat.shared.model.UserListItemDto
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import java.net.ConnectException
import java.net.SocketTimeoutException
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton

@Serializable
private data class SendMessageRequest(
    val id: String,
    val receiverId: String,
    val body: String,
    val sentAt: Long
)

@Singleton
internal class MessageRepositoryImpl @Inject constructor(
    private val dao: MessageDao,
    private val httpClient: HttpClient,
    private val settingsRepository: SettingsRepository,
    private val webSocketClient: WebSocketClient,
    @ApplicationScope private val scope: CoroutineScope
) : MessageRepository {

    init {
        scope.launch {
            webSocketClient.events.collect { event ->
                when (event) {
                    is WebSocketEvent.NewMessage -> {
                        val msg = event.message
                        dao.upsert(
                            MessageEntity(
                                id = msg.id,
                                senderId = msg.senderId,
                                senderUsername = msg.senderUsername,
                                receiverId = msg.receiverId,
                                body = msg.body,
                                sentAt = msg.sentAt,
                                isPending = false
                            )
                        )
                    }
                    is WebSocketEvent.Connected -> drainPendingMessages()
                    else -> Unit
                }
            }
        }
    }

    override fun getConversation(myId: String, otherId: String): Flow<List<MessageEntity>> =
        dao.getConversation(myId, otherId)

    override suspend fun sendMessage(recipientId: String, body: String) {
        val myId = settingsRepository.getValue(SettingsKeys.AUTH_USER_ID) ?: return
        val myUsername = settingsRepository.getValue(SettingsKeys.AUTH_USERNAME) ?: ""
        val localId = UUID.randomUUID().toString()
        val sentAt = System.currentTimeMillis()

        dao.upsert(
            MessageEntity(
                id = localId,
                senderId = myId,
                senderUsername = myUsername,
                receiverId = recipientId,
                body = body,
                sentAt = sentAt,
                isPending = true
            )
        )

        val result = attemptSendToServer(localId, recipientId, body, sentAt)
        if (result.isSuccess) {
            dao.markDelivered(localId)
        }
    }

    override suspend fun fetchUsers(): Result<List<UserListItemDto>> =
        withContext(Dispatchers.IO) {
            val serverUrl = settingsRepository.getValue(SettingsKeys.SERVER_URL)
                ?: return@withContext Result.failure(SyncError.NotConfigured("Server-URL nicht gesetzt"))
            val token = settingsRepository.getValue(SettingsKeys.AUTH_ACCESS_TOKEN)
                ?: return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet"))
            try {
                val response = httpClient.get("${serverUrl.trimEnd('/')}/api/users") {
                    header("Authorization", "Bearer $token")
                }
                if (response.status == HttpStatusCode.OK) {
                    Result.success(response.body<List<UserListItemDto>>())
                } else {
                    Result.failure(SyncError.ServerError(response.status.value, response.status.description))
                }
            } catch (e: SocketTimeoutException) {
                Result.failure(SyncError.Timeout(e))
            } catch (e: ConnectException) {
                Result.failure(SyncError.ConnectionError(e))
            } catch (e: Exception) {
                Result.failure(SyncError.Unknown(e))
            }
        }

    override suspend fun getMyUserId(): String? =
        settingsRepository.getValue(SettingsKeys.AUTH_USER_ID)

    override suspend fun drainPendingMessages() {
        val pending = withContext(Dispatchers.IO) { dao.getPendingMessages() }
        for (msg in pending) {
            val result = attemptSendToServer(msg.id, msg.receiverId, msg.body, msg.sentAt)
            if (result.isSuccess) {
                withContext(Dispatchers.IO) { dao.markDelivered(msg.id) }
            }
        }
    }

    private suspend fun attemptSendToServer(
        id: String,
        recipientId: String,
        body: String,
        sentAt: Long
    ): Result<Unit> = withContext(Dispatchers.IO) {
        val serverUrl = settingsRepository.getValue(SettingsKeys.SERVER_URL)
            ?: return@withContext Result.failure(SyncError.NotConfigured("Server-URL nicht gesetzt"))
        val token = settingsRepository.getValue(SettingsKeys.AUTH_ACCESS_TOKEN)
            ?: return@withContext Result.failure(SyncError.NotConfigured("Nicht angemeldet"))
        try {
            val response = httpClient.post("${serverUrl.trimEnd('/')}/api/messages") {
                header("Authorization", "Bearer $token")
                contentType(ContentType.Application.Json)
                setBody(SendMessageRequest(id = id, receiverId = recipientId, body = body, sentAt = sentAt))
            }
            if (response.status == HttpStatusCode.Created || response.status == HttpStatusCode.OK) {
                Result.success(Unit)
            } else {
                Result.failure(SyncError.ServerError(response.status.value, response.status.description))
            }
        } catch (e: SocketTimeoutException) {
            Result.failure(SyncError.Timeout(e))
        } catch (e: ConnectException) {
            Result.failure(SyncError.ConnectionError(e))
        } catch (e: Exception) {
            Result.failure(SyncError.Unknown(e))
        }
    }
}
