package de.ypchat.android.data.repository

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.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import de.ypchat.android.core.AppConfig
import de.ypchat.android.core.ProfileStore
import de.ypchat.android.core.SavedProfile
import de.ypchat.android.core.SessionCookieJar
import de.ypchat.android.data.api.RestApi
import de.ypchat.android.data.api.SocketClient
import de.ypchat.android.data.model.ChatMessageDto
import de.ypchat.android.data.model.CountryOption
import de.ypchat.android.data.model.FeedbackAdminLoginRequest
import de.ypchat.android.data.model.FeedbackItemDto
import de.ypchat.android.data.model.FeedbackRequest
import de.ypchat.android.data.model.HistoryItemDto
import de.ypchat.android.data.model.InboxItemDto
import de.ypchat.android.data.model.PartnerLinkDto
import de.ypchat.android.data.model.SocketEvent
import de.ypchat.android.data.model.UserDto
import okhttp3.MultipartBody
import java.util.Locale

class ChatRepository(
    private val restApi: RestApi,
    private val socketClient: SocketClient,
    private val cookieJar: SessionCookieJar,
    private val profileStore: ProfileStore
) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val _state = MutableStateFlow(ChatState())
    val state: StateFlow<ChatState> = _state.asStateFlow()
    private var timeoutTickerStarted = false

    init {
        scope.launch {
            socketClient.events.collect { event -> reduce(event) }
        }
        startTimeoutTicker()
    }

    suspend fun restoreSession() {
        _state.value = _state.value.copy(savedProfile = profileStore.read())
        loadCountries()
        loadFeedbackAdminStatus()
        runCatching { restApi.session() }
            .onSuccess { session ->
                _state.value = _state.value.copy(
                    expressSessionId = session.sessionId,
                    isLoggedIn = session.loggedIn && session.user != null,
                    currentUser = session.user,
                    errorMessage = null
                )
                if (session.loggedIn && session.user != null) {
                    resetTimeout()
                }
                connectSocket(session.sessionId)
            }
            .onFailure { error -> _state.value = _state.value.copy(errorMessage = error.message) }
    }

    suspend fun loadCountries() {
        runCatching { restApi.countries() }
            .onSuccess { countries ->
                val locale = Locale.getDefault()
                val options = countries.map { (englishName, code) ->
                    val normalizedCode = code.uppercase(Locale.US)
                    val localizedName = Locale.Builder().setRegion(normalizedCode).build().getDisplayCountry(locale)
                        .takeIf { it.isNotBlank() }
                        ?: englishName
                    CountryOption(
                        englishName = englishName,
                        displayName = localizedName,
                        isoCode = code
                    )
                }.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.displayName })

                _state.value = _state.value.copy(countries = options)
            }
            .onFailure { error ->
                _state.value = _state.value.copy(errorMessage = "Country list could not be loaded: ${error.message}")
            }
    }

    suspend fun login(userName: String, gender: String, age: Int, country: String) {
        profileStore.write(SavedProfile(userName, gender, age, country))
        val session = runCatching { restApi.session() }.getOrNull()
        val sessionId = session?.sessionId ?: _state.value.expressSessionId
        _state.value = _state.value.copy(expressSessionId = sessionId)
        if (!socketClient.isConnected) {
            connectSocket(sessionId)
        }
        socketClient.login(userName, gender, age, country, sessionId)
        resetTimeout()
    }

    suspend fun logout() {
        runCatching { restApi.logout() }
        socketClient.disconnect()
        cookieJar.clear()
        _state.value = ChatState(savedProfile = profileStore.read(), countries = _state.value.countries)
    }

    fun connectSocket(expressSessionId: String? = _state.value.expressSessionId) {
        socketClient.connect()
        expressSessionId?.let { socketClient.setSessionId(it) }
    }

    fun openConversation(userName: String) {
        _state.value = _state.value.copy(currentConversation = userName, messages = emptyList())
        socketClient.requestConversation(userName)
        resetTimeout()
    }

    fun closeConversation() {
        _state.value = _state.value.copy(currentConversation = null, messages = emptyList())
    }

    fun sendMessage(text: String) {
        val trimmed = text.trim()
        if (trimmed.isEmpty()) return
        val target = _state.value.currentConversation
        val isCommand = trimmed.startsWith("/")
        if (target == null && !isCommand) return

        socketClient.sendMessage(target, trimmed)
        if (!isCommand) {
            _state.value = _state.value.copy(
                messages = _state.value.messages + ChatMessageDto(
                    from = _state.value.currentUser?.userName.orEmpty(),
                    to = target,
                    message = trimmed,
                    timestamp = java.time.Instant.now().toString()
                )
            )
        }
        resetTimeout()
    }

    fun sendImage(toUserName: String, imageCode: String, imageUrl: String) {
        val absoluteUrl = if (imageUrl.startsWith("http")) imageUrl else AppConfig.baseUrl + imageUrl
        socketClient.sendImage(toUserName, imageCode, absoluteUrl)
        _state.value = _state.value.copy(
            messages = _state.value.messages + ChatMessageDto(
                from = _state.value.currentUser?.userName.orEmpty(),
                to = toUserName,
                message = absoluteUrl,
                timestamp = java.time.Instant.now().toString(),
                isImage = true,
                imageUrl = absoluteUrl,
                imageCode = imageCode
            )
        )
        resetTimeout()
    }

    fun setImageUploadState(inProgress: Boolean, message: String? = null) {
        _state.value = _state.value.copy(
            isUploadingImage = inProgress,
            imageUploadMessage = message
        )
    }

    suspend fun uploadImage(part: MultipartBody.Part) = restApi.uploadImage(part)

    suspend fun loadFeedback() {
        runCatching { restApi.feedback() }
            .onSuccess { response ->
                _state.value = _state.value.copy(feedbackItems = response.items, feedbackMessage = null)
            }
            .onFailure { error ->
                _state.value = _state.value.copy(feedbackMessage = error.message)
            }
    }

    suspend fun submitFeedback(comment: String) {
        val trimmed = comment.trim()
        if (trimmed.isEmpty()) return

        val profile = _state.value.savedProfile
        runCatching {
            restApi.submitFeedback(
                FeedbackRequest(
                    name = profile.nickname,
                    age = profile.age,
                    country = profile.country,
                    gender = profile.gender,
                    comment = trimmed
                )
            )
        }.onSuccess {
            _state.value = _state.value.copy(feedbackMessage = "Feedback saved")
            loadFeedback()
        }.onFailure { error ->
            _state.value = _state.value.copy(feedbackMessage = error.message)
        }
    }

    suspend fun loadFeedbackAdminStatus() {
        runCatching { restApi.feedbackAdminStatus() }
            .onSuccess { response ->
                _state.value = _state.value.copy(
                    feedbackAdminAuthenticated = response.authenticated,
                    feedbackAdminUserName = response.username,
                    feedbackAdminError = null
                )
            }
            .onFailure {
                _state.value = _state.value.copy(
                    feedbackAdminAuthenticated = false,
                    feedbackAdminUserName = null
                )
            }
    }

    suspend fun loginFeedbackAdmin(username: String, password: String) {
        runCatching { restApi.feedbackAdminLogin(FeedbackAdminLoginRequest(username, password)) }
            .onSuccess { response ->
                _state.value = _state.value.copy(
                    feedbackAdminAuthenticated = true,
                    feedbackAdminUserName = response.username,
                    feedbackAdminError = null
                )
                loadFeedback()
            }
            .onFailure { error ->
                _state.value = _state.value.copy(feedbackAdminError = error.message)
            }
    }

    suspend fun logoutFeedbackAdmin() {
        runCatching { restApi.feedbackAdminLogout() }
        _state.value = _state.value.copy(
            feedbackAdminAuthenticated = false,
            feedbackAdminUserName = null,
            feedbackAdminError = null
        )
    }

    suspend fun deleteFeedback(id: String) {
        runCatching { restApi.deleteFeedback(id) }
            .onSuccess { loadFeedback() }
            .onFailure { error ->
                _state.value = _state.value.copy(feedbackAdminError = error.message)
            }
    }

    suspend fun loadPartners() {
        runCatching { restApi.partners() }
            .onSuccess { links ->
                _state.value = _state.value.copy(partnerLinks = links, partnersError = null)
            }
            .onFailure { error ->
                _state.value = _state.value.copy(partnersError = error.message)
            }
    }

    fun search(nameIncludes: String?, minAge: Int?, maxAge: Int?, countries: List<String>, genders: List<String>) {
        socketClient.userSearch(nameIncludes, minAge, maxAge, countries, genders)
        resetTimeout()
    }

    fun requestInbox() {
        socketClient.requestOpenConversations()
        resetTimeout()
    }

    fun requestHistory() {
        socketClient.requestHistory()
        resetTimeout()
    }

    fun blockUser(userName: String) {
        socketClient.blockUser(userName)
        resetTimeout()
    }

    fun unblockUser(userName: String) {
        socketClient.unblockUser(userName)
        resetTimeout()
    }

    private fun startTimeoutTicker() {
        if (timeoutTickerStarted) return
        timeoutTickerStarted = true
        scope.launch {
            while (isActive) {
                delay(1_000)
                val current = _state.value
                if (!current.isLoggedIn) continue
                val next = (current.remainingSecondsToTimeout - 1).coerceAtLeast(0)
                _state.value = current.copy(remainingSecondsToTimeout = next)
                if (next == 0) {
                    logout()
                }
            }
        }
    }

    private fun resetTimeout() {
        if (_state.value.isLoggedIn || _state.value.currentUser != null) {
            _state.value = _state.value.copy(remainingSecondsToTimeout = 1800)
        }
    }

    private fun reduce(event: SocketEvent) {
        val current = _state.value
        _state.value = when (event) {
            is SocketEvent.ConnectionChanged -> current.copy(isConnected = event.connected)
            is SocketEvent.Connected -> {
                event.sessionId?.let { socketClient.setSessionId(it) }
                current.copy(
                    expressSessionId = event.sessionId ?: current.expressSessionId,
                    isLoggedIn = event.loggedIn || current.isLoggedIn,
                    currentUser = event.user ?: current.currentUser,
                    errorMessage = null,
                    remainingSecondsToTimeout = if (event.loggedIn || current.isLoggedIn) 1800 else current.remainingSecondsToTimeout
                )
            }
            is SocketEvent.LoginSuccess -> current.copy(
                expressSessionId = event.sessionId ?: current.expressSessionId,
                isLoggedIn = true,
                currentUser = event.user,
                errorMessage = null,
                remainingSecondsToTimeout = 1800
            )
            is SocketEvent.UserList -> current.copy(users = event.users)
            is SocketEvent.IncomingMessage -> {
                val active = current.currentConversation == event.message.from
                current.copy(
                    messages = if (active) current.messages + event.message else current.messages,
                    unreadChatsCount = if (active) current.unreadChatsCount else current.unreadChatsCount + 1,
                    remainingSecondsToTimeout = 1800
                )
            }
            is SocketEvent.MessageSent -> current
            is SocketEvent.Conversation -> current.copy(
                currentConversation = event.withUserName,
                messages = event.messages,
                unreadChatsCount = maxOf(0, current.unreadChatsCount - 1),
                remainingSecondsToTimeout = 1800
            )
            is SocketEvent.SearchResults -> current.copy(searchResults = event.results)
            is SocketEvent.HistoryResults -> current.copy(historyResults = event.results)
            is SocketEvent.InboxResults -> current.copy(inboxResults = event.results)
            is SocketEvent.UnreadChats -> current.copy(unreadChatsCount = event.count)
            is SocketEvent.UserBlocked -> current.copy(errorMessage = "${event.userName} blocked")
            is SocketEvent.UserUnblocked -> current.copy(errorMessage = "${event.userName} unblocked")
            is SocketEvent.CommandResult -> current.copy(
                commandLines = event.lines,
                commandKind = event.kind,
                commandTable = null,
                awaitingLoginUsername = event.kind == "loginPromptUsername",
                awaitingLoginPassword = event.kind == "loginPromptPassword",
                errorMessage = if (event.kind == "info" || event.kind.startsWith("login")) event.lines.joinToString(" | ") else current.errorMessage
            )
            is SocketEvent.CommandTable -> current.copy(
                commandLines = emptyList(),
                commandKind = null,
                commandTable = CommandTableState(event.title, event.columns, event.rows)
            )
            is SocketEvent.Error -> current.copy(errorMessage = event.message)
        }
    }
}

data class CommandTableState(
    val title: String,
    val columns: List<String>,
    val rows: List<List<String>>
)

data class ChatState(
    val isConnected: Boolean = false,
    val isLoggedIn: Boolean = false,
    val expressSessionId: String? = null,
    val currentUser: UserDto? = null,
    val users: List<UserDto> = emptyList(),
    val currentConversation: String? = null,
    val messages: List<ChatMessageDto> = emptyList(),
    val searchResults: List<UserDto> = emptyList(),
    val inboxResults: List<InboxItemDto> = emptyList(),
    val historyResults: List<HistoryItemDto> = emptyList(),
    val countries: List<CountryOption> = emptyList(),
    val feedbackItems: List<FeedbackItemDto> = emptyList(),
    val feedbackMessage: String? = null,
    val feedbackAdminAuthenticated: Boolean = false,
    val feedbackAdminUserName: String? = null,
    val feedbackAdminError: String? = null,
    val partnerLinks: List<PartnerLinkDto> = emptyList(),
    val partnersError: String? = null,
    val savedProfile: SavedProfile = SavedProfile(),
    val commandLines: List<String> = emptyList(),
    val commandKind: String? = null,
    val commandTable: CommandTableState? = null,
    val awaitingLoginUsername: Boolean = false,
    val awaitingLoginPassword: Boolean = false,
    val remainingSecondsToTimeout: Int = 1800,
    val isUploadingImage: Boolean = false,
    val imageUploadMessage: String? = null,
    val unreadChatsCount: Int = 0,
    val errorMessage: String? = null
)
