package app.closer.domain.usecase

import app.closer.domain.model.Couple
import app.closer.domain.model.GameType
import app.closer.domain.model.QuestionSession
import app.closer.domain.repository.AuthRepository
import app.closer.domain.repository.CoupleRepository
import app.closer.domain.repository.QuestionSessionRepository
import app.closer.domain.repository.UserRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton

/**
 * Identifies a started game session plus the couple it belongs to, so a caller
 * can finish exactly that session later without re-resolving the couple.
 */
data class GameHandle(
    val sessionId: String,
    val coupleId: String
)

/**
 * Manages game session lifecycle with partner notifications.
 * Enforces "one active game per couple" rule.
 */
@Singleton
class GameSessionManager @Inject constructor(
    private val sessionRepository: QuestionSessionRepository,
    private val authRepository: AuthRepository,
    private val coupleRepository: CoupleRepository,
    private val userRepository: UserRepository
) {
    val currentUserId: String?
        get() = authRepository.currentUserId

    suspend fun getCoupleForUser(userId: String): Couple? =
        coupleRepository.getCoupleForUser(userId)

    suspend fun getUser(userId: String) =
        userRepository.getUser(userId)

    /**
     * Start a new game session for the current couple.
     * Checks for existing active sessions and notifies partner.
     */
    suspend fun startGame(
        userId: String,
        gameType: String,
        categoryId: String? = null,
        questionIds: List<String>? = null
    ): Result<String> {
        val couple = coupleRepository.getCoupleForUser(userId)
            ?: return Result.failure(Exception("User is not in a couple"))
        return startGameWithCouple(userId, couple, gameType, categoryId, questionIds)
            .map { it.id }
    }

    /**
     * Start a game for the currently signed-in user, resolving their couple once.
     * Returns a [GameHandle] on success. Fails if the user is solo or a session
     * is already active (the failure message carries partner/game context).
     */
    suspend fun startGameForCurrentUser(
        gameType: String,
        categoryId: String? = null,
        questionIds: List<String>? = null
    ): Result<GameHandle> {
        val userId = authRepository.currentUserId
            ?: return Result.failure(Exception("Not signed in"))
        val couple = coupleRepository.getCoupleForUser(userId)
            ?: return Result.failure(Exception("User is not in a couple"))
        return startGameWithCouple(userId, couple, gameType, categoryId, questionIds)
            .map { session -> GameHandle(session.id, couple.id) }
    }

    private suspend fun startGameWithCouple(
        userId: String,
        couple: Couple,
        gameType: String,
        categoryId: String?,
        questionIds: List<String>?
    ): Result<QuestionSession> {
        val activeSession = sessionRepository.getActiveSessionForCouple(couple.id)
        if (activeSession != null) {
            val partnerId = couple.userIds.firstOrNull { it != userId }
            val partnerName = partnerId
                ?.let { runCatching { userRepository.getUser(it) }.getOrNull() }
                ?.displayName ?: "Partner"
            return Result.failure(
                Exception("partner_active_session|$partnerName|${gameTypeLabel(gameType)}")
            )
        }

        val session = QuestionSession(
            coupleId = couple.id,
            categoryId = categoryId ?: "",
            questionIds = questionIds ?: emptyList(),
            startedByUserId = userId,
            gameType = gameType,
            status = "active"
        )

        // Use the id assigned by the repository so the started game observes the right doc
        // (an empty id builds an invalid Firestore path and crashes the game on open).
        return sessionRepository.saveSession(session).map { id -> session.copy(id = id) }
    }

    /**
     * Finish the session referenced by [handle].
     */
    suspend fun finishGameForCurrentUser(handle: GameHandle): Result<Unit> =
        finishGame(handle.sessionId, handle.coupleId)

    /**
     * Mark the session as completed for the couple.
     * The first caller wins — the whole session flips to "completed" immediately.
     * This is correct for pass-the-phone games; async partner-sync games would
     * need per-user completion fields instead.
     */
    suspend fun finishGame(
        sessionId: String,
        coupleId: String
    ): Result<Unit> = runCatching {
        val currentSession = sessionRepository.getActiveSessionForCouple(coupleId)
            ?: throw Exception("No active session found")

        if (currentSession.id != sessionId) {
            throw Exception("Session ID mismatch")
        }

        val completedAt = System.currentTimeMillis()
        val updatedSession = currentSession.copy(
            completedAt = completedAt,
            status = "completed"
        )

        // Propagate a failed write: saveSession returns Result.failure rather than
        // throwing, so without getOrThrow() the outer runCatching would report success
        // and leave the session stuck "active" (locking the couple out of new games).
        sessionRepository.saveSession(updatedSession).map { }.getOrThrow()
    }

    /**
     * Get the active session for a couple.
     */
    suspend fun getActiveSession(coupleId: String): QuestionSession? =
        sessionRepository.getActiveSessionForCouple(coupleId)

    /**
     * Check if a couple has an active session.
     */
    suspend fun hasActiveSession(coupleId: String): Boolean =
        sessionRepository.hasActiveSession(coupleId)

    /**
     * Mark the current user's side of the session as complete.
     * The session is automatically finished once both users have marked done.
     * Safe to call concurrently — a Firestore transaction ensures idempotency.
     */
    suspend fun markUserComplete(sessionId: String, coupleId: String): Result<Unit> {
        val userId = authRepository.currentUserId
            ?: return Result.failure(Exception("Not signed in"))
        return sessionRepository.markUserComplete(sessionId, coupleId, userId)
    }

    /**
     * Force-finish the couple's active session without requiring both users to mark done.
     * Use when one partner wants to release the lock (e.g. partner never played).
     */
    suspend fun abandonSession(coupleId: String): Result<Unit> =
        sessionRepository.abandonSession(coupleId)

    /**
     * Observe active session changes for a couple.
     */
    fun observeActiveSession(coupleId: String): Flow<QuestionSession?> =
        sessionRepository.observeActiveSessionForCouple(coupleId)

    private fun gameTypeLabel(gameType: String): String = when (gameType) {
        GameType.WHEEL -> "Wheel"
        GameType.THIS_OR_THAT -> "This or That"
        GameType.HOW_WELL -> "How Well Do You Know Me"
        GameType.DESIRE_SYNC -> "Desire Sync"
        else -> gameType
    }
}
