package app.closer.ui.questions

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.closer.crypto.SealedRevealManager
import app.closer.data.local.QuestionDao
import app.closer.data.local.mapper.toQuestion
import app.closer.domain.model.ChoiceAnswerConfigImpl
import app.closer.domain.model.Question
import app.closer.domain.model.QuestionAnswer
import app.closer.domain.model.QuestionMessage
import app.closer.domain.model.QuestionReaction
import app.closer.domain.repository.QuestionThreadRepository
import com.google.firebase.auth.FirebaseAuth
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

enum class QuestionPhase { INPUT, WAITING, REVEALED }

data class QuestionThreadUiState(
    val isLoading: Boolean = true,
    val error: String? = null,
    val question: Question? = null,
    val threadId: String? = null,
    val phase: QuestionPhase = QuestionPhase.INPUT,
    val myAnswer: QuestionAnswer? = null,
    val partnerAnswer: QuestionAnswer? = null,
    val partnerPhotoUrl: String? = null,
    val messages: List<QuestionMessage> = emptyList(),
    val reactions: List<QuestionReaction> = emptyList(),
    val pendingWrittenText: String = "",
    val pendingSelectedOptionIds: List<String> = emptyList(),
    val pendingScaleValue: Int = 3,
    val messageInput: String = "",
    val helpExpanded: Boolean = false,
    val isSubmitting: Boolean = false,
    val previousQuestionId: String? = null,
    val nextQuestionId: String? = null
)

@HiltViewModel
class QuestionThreadViewModel @Inject constructor(
    private val repository: QuestionThreadRepository,
    private val questionDao: QuestionDao,
    private val sealedRevealManager: SealedRevealManager,
    private val activeThreadMonitor: app.closer.notifications.ActiveThreadMonitor,
    private val localAnswerRepository: app.closer.domain.repository.LocalAnswerRepository,
    private val userRepository: app.closer.domain.repository.UserRepository,
    private val coupleRepository: app.closer.domain.repository.CoupleRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val coupleId: String = savedStateHandle["coupleId"] ?: ""
    private val questionId: String = savedStateHandle["questionId"] ?: ""
    val currentUserId: String = FirebaseAuth.getInstance().currentUser?.uid ?: ""

    // Released-once guard for our thread reveal key.
    private var threadKeyReleased = false

    // True when the matching daily question was already answered + revealed in the daily flow, so
    // the discussion (chat) should open directly here rather than asking the user to re-answer.
    private var dailyRevealed = false

    private val _uiState = MutableStateFlow(
        QuestionThreadUiState(
            previousQuestionId = savedStateHandle["prevId"],
            nextQuestionId = savedStateHandle["nextId"]
        )
    )
    val uiState: StateFlow<QuestionThreadUiState> = _uiState.asStateFlow()

    init {
        // While this thread is open, suppress its own "new message" notifications (the user is
        // reading it live). Cleared in onCleared when they leave.
        activeThreadMonitor.enter(questionId)
        loadThread()
    }

    override fun onCleared() {
        super.onCleared()
        activeThreadMonitor.leave(questionId)
    }

    private fun loadThread() {
        viewModelScope.launch {
            try {
                val entity = questionDao.getQuestionById(questionId)
                val question = entity?.toQuestion()
                if (question == null) {
                    _uiState.update { it.copy(isLoading = false, error = "Question not found") }
                    return@launch
                }
                _uiState.update { it.copy(question = question, isLoading = false) }

                // If this question's daily reveal is already complete, skip the answer phase and
                // open the chat directly — the answers were already given/seen in the daily flow.
                dailyRevealed = runCatching {
                    localAnswerRepository.getAnswer(questionId)?.isRevealed == true
                }.getOrDefault(false)

                val threadId = repository.findOrCreateThreadId(coupleId, questionId, question.category, currentUserId)
                _uiState.update { it.copy(threadId = threadId) }

                // Load both partners' avatars so each chat message can show its sender's photo,
                // like a modern messaging thread.
                launch {
                    val couple = runCatching { coupleRepository.getCoupleForUser(currentUserId) }.getOrNull()
                    val partnerId = couple?.userIds?.firstOrNull { it != currentUserId }
                    val partnerPhoto = partnerId?.let {
                        runCatching { userRepository.getUser(it)?.photoUrl }.getOrNull()
                    }
                    _uiState.update { it.copy(partnerPhotoUrl = partnerPhoto) }
                }

                launch {
                    repository.observeAnswers(coupleId, threadId).collect { answers ->
                        handleAnswers(threadId, answers)
                    }
                }
                launch {
                    repository.observeMessages(coupleId, threadId).collect { msgs ->
                        _uiState.update { it.copy(messages = msgs) }
                    }
                }
                launch {
                    repository.observeReactions(coupleId, threadId).collect { reactions ->
                        _uiState.update { it.copy(reactions = reactions) }
                    }
                }
            } catch (e: Exception) {
                _uiState.update { it.copy(isLoading = false, error = e.message) }
            }
        }
    }

    /**
     * Drives the thread reveal entirely on-device. Both answers are sealed (schemaVersion 3):
     * once both partners have answered, we release our one-time key (so the partner can decrypt
     * us) and decrypt their answer with our private key. Our own answer is decrypted with the
     * one-time key held locally. The server never sees plaintext or any usable key.
     */
    private suspend fun handleAnswers(threadId: String, answers: List<QuestionAnswer>) {
        val mySealed = answers.find { it.userId == currentUserId }
        val partnerSealed = answers.find { it.userId != currentUserId }
        when {
            // Both answered IN this thread (e.g. a question pack answered here) — native reveal.
            mySealed != null && partnerSealed != null -> {
                // Release our key so the partner can decrypt us, then decrypt theirs.
                releaseThreadKeyOnce(threadId, partnerSealed.userId)
                val mine = decryptOwn(threadId, mySealed)
                val partner = decryptPartner(threadId, partnerSealed)
                if (partner != null) {
                    _uiState.update { it.copy(phase = QuestionPhase.REVEALED, myAnswer = mine, partnerAnswer = partner) }
                } else {
                    // Partner answered but hasn't released their key yet — keep waiting.
                    _uiState.update { it.copy(phase = QuestionPhase.WAITING, myAnswer = mine, partnerAnswer = null) }
                }
            }

            // Daily question already revealed in the daily flow → open the chat directly so the
            // couple can message about it (no re-answering needed here).
            dailyRevealed ->
                _uiState.update { it.copy(phase = QuestionPhase.REVEALED, myAnswer = null, partnerAnswer = null) }

            mySealed != null ->
                _uiState.update {
                    it.copy(phase = QuestionPhase.WAITING, myAnswer = decryptOwn(threadId, mySealed), partnerAnswer = null)
                }

            else ->
                _uiState.update { it.copy(phase = QuestionPhase.INPUT, myAnswer = null, partnerAnswer = null) }
        }
    }

    private suspend fun releaseThreadKeyOnce(threadId: String, partnerId: String) {
        if (threadKeyReleased) return
        val released = runCatching {
            sealedRevealManager.releaseOwnKeyForThread(coupleId, threadId, currentUserId, partnerId)
        }.getOrDefault(false)
        if (released) threadKeyReleased = true
    }

    private suspend fun decryptOwn(threadId: String, sealed: QuestionAnswer): QuestionAnswer {
        val payload = sealed.encryptedPayload ?: return sealed
        val decrypted = runCatching {
            sealedRevealManager.decryptOwnThreadAnswer(coupleId, threadId, currentUserId, payload)
        }.getOrNull() ?: return sealed
        return sealed.copy(
            writtenText = decrypted.writtenText,
            selectedOptionIds = decrypted.selectedOptionIds,
            scaleValue = decrypted.scaleValue
        )
    }

    private suspend fun decryptPartner(threadId: String, sealed: QuestionAnswer): QuestionAnswer? {
        val payload = sealed.encryptedPayload ?: return null
        val decrypted = runCatching {
            sealedRevealManager.decryptPartnerThreadAnswer(coupleId, threadId, sealed.userId, currentUserId, payload)
        }.getOrNull() ?: return null
        return sealed.copy(
            writtenText = decrypted.writtenText,
            selectedOptionIds = decrypted.selectedOptionIds,
            scaleValue = decrypted.scaleValue
        )
    }

    // ─── Answer input mutations ──────────────────────────────────────────────────

    fun updateWrittenText(text: String) {
        _uiState.update { it.copy(pendingWrittenText = text.take(MAX_ANSWER_LENGTH)) }
    }

    fun toggleOption(optionId: String) {
        _uiState.update { state ->
            val question = state.question ?: return@update state
            val current = state.pendingSelectedOptionIds
            val updated = if (question.type == "single_choice") {
                listOf(optionId)
            } else if (question.type == "multi_choice") {
                val maxSel = (question.answerConfig as? ChoiceAnswerConfigImpl)?.config?.maxSelections ?: 0
                when {
                    optionId in current -> current - optionId
                    maxSel == 0 || current.size < maxSel -> current + optionId
                    else -> current
                }
            } else {
                if (optionId in current) current - optionId else current + optionId
            }
            state.copy(pendingSelectedOptionIds = updated)
        }
    }

    fun updateScale(value: Int) {
        _uiState.update { it.copy(pendingScaleValue = value) }
    }

    fun submitAnswer() {
        val state = _uiState.value
        val threadId = state.threadId ?: return
        val question = state.question ?: return
        if (state.isSubmitting) return
        if (currentUserId.isEmpty()) return

        _uiState.update { it.copy(isSubmitting = true) }
        viewModelScope.launch {
            try {
                val answer = buildAnswer(state, question)
                repository.submitAnswer(coupleId, threadId, currentUserId, answer)
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message) }
            } finally {
                _uiState.update { it.copy(isSubmitting = false) }
            }
        }
    }

    private fun buildAnswer(state: QuestionThreadUiState, question: Question): QuestionAnswer {
        val now = System.currentTimeMillis()
        return QuestionAnswer(
            userId = currentUserId,
            questionId = questionId,
            answerType = question.type,
            writtenText = if (question.type == "written") state.pendingWrittenText else null,
            selectedOptionIds = when (question.type) {
                "single_choice", "multi_choice", "this_or_that" -> state.pendingSelectedOptionIds
                else -> emptyList()
            },
            scaleValue = if (question.type == "scale") state.pendingScaleValue else null,
            createdAt = now,
            updatedAt = now
        )
    }

    fun canSubmit(): Boolean {
        val state = _uiState.value
        val question = state.question ?: return false
        if (currentUserId.isEmpty()) return false
        return when (question.type) {
            "written" -> state.pendingWrittenText.isNotBlank()
            "single_choice", "this_or_that" -> state.pendingSelectedOptionIds.isNotEmpty()
            "multi_choice" -> state.pendingSelectedOptionIds.isNotEmpty()
            "scale" -> true
            else -> false
        }
    }

    // ─── Help expandable ─────────────────────────────────────────────────────────

    fun toggleHelp() {
        _uiState.update { it.copy(helpExpanded = !it.helpExpanded) }
    }

    // ─── Discussion ──────────────────────────────────────────────────────────────

    fun updateMessageInput(text: String) {
        _uiState.update { it.copy(messageInput = text.take(MAX_MESSAGE_LENGTH)) }
    }

    fun sendMessage() {
        val state = _uiState.value
        val threadId = state.threadId ?: return
        if (state.phase != QuestionPhase.REVEALED) return
        if (currentUserId.isEmpty()) return
        val text = state.messageInput.trim()
        if (text.isBlank()) return

        _uiState.update { it.copy(messageInput = "") }
        viewModelScope.launch {
            try {
                val msg = QuestionMessage(
                    userId = currentUserId,
                    text = text,
                    createdAt = System.currentTimeMillis()
                )
                repository.sendMessage(coupleId, threadId, msg)
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message) }
            }
        }
    }

    fun sendImage(imageBytes: ByteArray) {
        val state = _uiState.value
        val threadId = state.threadId ?: return
        if (state.phase != QuestionPhase.REVEALED) return
        if (currentUserId.isEmpty() || imageBytes.isEmpty()) return
        viewModelScope.launch {
            runCatching { repository.sendImageMessage(coupleId, threadId, currentUserId, imageBytes) }
                .onFailure { e -> _uiState.update { it.copy(error = e.message ?: "Couldn't send the photo.") } }
        }
    }

    /** Downloads + decrypts an image message's bytes for display (called lazily by the UI). */
    suspend fun loadDecryptedMedia(mediaUrl: String): ByteArray? =
        repository.loadDecryptedMedia(coupleId, mediaUrl)

    // ─── Reactions ───────────────────────────────────────────────────────────────

    fun addReaction(targetUserId: String, emoji: String) {
        val state = _uiState.value
        val threadId = state.threadId ?: return
        if (state.phase != QuestionPhase.REVEALED) return
        if (currentUserId.isEmpty()) return

        viewModelScope.launch {
            try {
                val reaction = QuestionReaction(
                    userId = currentUserId,
                    targetUserId = targetUserId,
                    emoji = emoji,
                    createdAt = System.currentTimeMillis()
                )
                repository.addReaction(coupleId, threadId, reaction)
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message) }
            }
        }
    }

    fun dismissError() {
        _uiState.update { it.copy(error = null) }
    }

    companion object {
        const val MAX_ANSWER_LENGTH = 2000
        const val MAX_MESSAGE_LENGTH = 500
    }
}
