package app.closer.domain

import app.closer.domain.model.DateCostLevel
import app.closer.domain.model.DateSuggestion
import app.closer.domain.model.DateSuggestionTrigger
import app.closer.domain.model.SourceFlag

/**
 * Pure, deterministic logic for deciding when to suggest a date idea.
 *
 * No Android dependencies. The engine receives a neutral summary of recent couple activity
 * and returns a prioritized list of [DateSuggestion]s. Callers (e.g., a ViewModel or Home
 * priority engine) decide whether to show, queue, or dismiss each suggestion.
 *
 * Privacy rules enforced here:
 * - Hidden private answer text is never read or surfaced.
 * - Revealed answers may influence suggestions but are explicitly flagged as
 *   [SourceFlag.REVEALED_ANSWER]; the original text is not included in the output.
 * - Notification copy and suggestion text use only safe category metadata and
 *   curated activity phrases.
 */
object DateSuggestionEngine {

    /**
     * Number of answers in a single category required to trigger a category-surge suggestion.
     */
    internal const val CATEGORY_SURGE_THRESHOLD = 3

    /**
     * Suggests dates based on the provided [input].
     *
     * The returned list is sorted by descending priority. Callers should normally show the
     * highest-priority suggestion and suppress duplicates for the same trigger/category pair.
     *
     * @param input a snapshot of recent couple activity
     * @return zero or more date suggestions, highest priority first
     */
    fun suggest(input: DateSuggestionInput): List<DateSuggestion> {
        val suggestions = mutableListOf<DateSuggestion>()

        suggestions += categorySurgeSuggestions(input)
        suggestions += recapCompletionSuggestion(input)
        suggestions += challengeCompletionSuggestion(input)
        suggestions += partnerSavedDateSuggestion(input)
        suggestions += sharedInterestRevealSuggestion(input)

        return suggestions.sortedByDescending { it.priority }
    }

    // -----------------------------------------------------------------
    // Trigger: 3 answered questions in one category
    // -----------------------------------------------------------------

    internal fun categorySurgeSuggestions(input: DateSuggestionInput): List<DateSuggestion> {
        if (input.answeredQuestionsByCategory.isEmpty()) return emptyList()

        val categoryCounts = input.answeredQuestionsByCategory
            .groupingBy { it.categoryId }
            .eachCount()
            .filterValues { it >= CATEGORY_SURGE_THRESHOLD }
            .toList()
            .sortedByDescending { (_, count) -> count }

        return categoryCounts.map { (categoryId, count) ->
            val activity = activityForCategory(
                categoryId = categoryId,
                titleCatalog = input.dateIdeaTitleCatalog,
                fallback = "A small date idea based on what you've explored together"
            )
            val costLevel = costLevelForCategory(categoryId)
            val duration = durationForCategory(categoryId)
            DateSuggestion(
                triggerReason = DateSuggestionTrigger.CATEGORY_SURGE,
                suggestedActivity = "$activity ($duration · ${costLabel(costLevel)})",
                priority = count.coerceAtLeast(CATEGORY_SURGE_THRESHOLD) * 10,
                categoryId = categoryId,
                sourceFlags = listOf(SourceFlag.CATEGORY_METADATA)
            )
        }
    }

    // -----------------------------------------------------------------
    // Trigger: completed weekly recap
    // -----------------------------------------------------------------

    internal fun recapCompletionSuggestion(input: DateSuggestionInput): List<DateSuggestion> {
        if (!input.weeklyRecapCompleted) return emptyList()

        val favoriteCategory = input.weeklyRecapFavoriteCategory
        val activity = activityForCategory(
            categoryId = favoriteCategory,
            titleCatalog = input.dateIdeaTitleCatalog,
            fallback = "Plan something together this week"
        )
        val costLevel = costLevelForCategory(favoriteCategory)
        val duration = durationForCategory(favoriteCategory)

        return listOf(
            DateSuggestion(
                triggerReason = DateSuggestionTrigger.WEEKLY_RECAP_COMPLETED,
                suggestedActivity = "$activity ($duration · ${costLabel(costLevel)})",
                priority = 35,
                categoryId = favoriteCategory,
                sourceFlags = listOf(SourceFlag.RECAP_CONTEXT, SourceFlag.CATEGORY_METADATA)
            )
        )
    }

    // -----------------------------------------------------------------
    // Trigger: completed challenge
    // -----------------------------------------------------------------

    internal fun challengeCompletionSuggestion(input: DateSuggestionInput): List<DateSuggestion> {
        if (!input.challengeCompleted) return emptyList()

        val challengeCategory = input.completedChallengeCategory
        val activity = activityForCategory(
            categoryId = challengeCategory,
            titleCatalog = input.dateIdeaTitleCatalog,
            fallback = "Celebrate finishing your challenge with a date"
        )
        val costLevel = costLevelForCategory(challengeCategory)
        val duration = durationForCategory(challengeCategory)

        return listOf(
            DateSuggestion(
                triggerReason = DateSuggestionTrigger.CHALLENGE_COMPLETED,
                suggestedActivity = "$activity ($duration · ${costLabel(costLevel)})",
                priority = 40,
                categoryId = challengeCategory,
                sourceFlags = listOf(SourceFlag.CHALLENGE_CONTEXT, SourceFlag.CATEGORY_METADATA)
            )
        )
    }

    // -----------------------------------------------------------------
    // Trigger: a partner saves a date idea
    // -----------------------------------------------------------------

    internal fun partnerSavedDateSuggestion(input: DateSuggestionInput): List<DateSuggestion> {
        if (input.partnerSavedDateIdeaIds.isEmpty()) return emptyList()

        return input.partnerSavedDateIdeaIds.mapIndexed { index, ideaId ->
            val title = input.dateIdeaTitleCatalog[ideaId]
                ?: "A date idea your partner saved"
            val categoryId = input.dateIdeaCategoryById[ideaId]
            val costLevel = costLevelForCategory(categoryId)
            val duration = durationForCategory(categoryId)
            DateSuggestion(
                triggerReason = DateSuggestionTrigger.PARTNER_SAVED_DATE_IDEA,
                suggestedActivity = "$title ($duration · ${costLabel(costLevel)})",
                priority = 45 - index.coerceAtLeast(0).coerceAtMost(5) * 3,
                categoryId = categoryId,
                sourceFlags = listOf(SourceFlag.PARTNER_ACTION)
            )
        }
    }

    // -----------------------------------------------------------------
    // Trigger: reveal indicates shared interest, if already revealed
    // -----------------------------------------------------------------

    internal fun sharedInterestRevealSuggestion(input: DateSuggestionInput): List<DateSuggestion> {
        if (!input.hasSharedInterestReveal) return emptyList()

        val categoryId = input.sharedInterestCategory
        val activity = activityForCategory(
            categoryId = categoryId,
            titleCatalog = input.dateIdeaTitleCatalog,
            fallback = "A date idea inspired by something you both revealed"
        )
        val costLevel = costLevelForCategory(categoryId)
        val duration = durationForCategory(categoryId)

        return listOf(
            DateSuggestion(
                triggerReason = DateSuggestionTrigger.SHARED_INTEREST_REVEALED,
                suggestedActivity = "$activity ($duration · ${costLabel(costLevel)})",
                priority = 50,
                categoryId = categoryId,
                sourceFlags = listOf(SourceFlag.REVEALED_ANSWER, SourceFlag.CATEGORY_METADATA)
            )
        )
    }

    // -----------------------------------------------------------------
    // Category-to-activity mapping
    // -----------------------------------------------------------------

    /**
     * Returns a curated activity phrase for [categoryId]. If a caller-supplied catalog
     * contains a title keyed by [categoryId], that title is used; otherwise a safe default
     * phrase is chosen from the built-in map. [fallback] is used only when no category is given.
     */
    internal fun activityForCategory(
        categoryId: String?,
        titleCatalog: Map<String, String>,
        fallback: String
    ): String {
        if (categoryId.isNullOrBlank()) return fallback
        return titleCatalog[categoryId] ?: defaultActivityForCategory(categoryId)
    }

    internal fun defaultActivityForCategory(categoryId: String): String = when (categoryId.lowercase()) {
        "adventure" -> "Try a new outdoor experience together"
        "travel" -> "Plan a mini local adventure"
        "food" -> "Cook or taste something new together"
        "learning" -> "Learn something new as a pair"
        "romance" -> "A quiet, intentional evening together"
        "intimacy" -> "A private, connected moment together"
        "connection" -> "A low-pressure way to check in over something fun"
        "seasonal" -> "A seasonal date that fits this time of year"
        "play" -> "A playful, lighthearted activity together"
        else -> "A small date idea based on what you've explored together"
    }

    internal fun costLevelForCategory(categoryId: String?): DateCostLevel = when (categoryId?.lowercase()) {
        "adventure", "travel" -> DateCostLevel.LOW
        "food" -> DateCostLevel.MEDIUM
        "learning" -> DateCostLevel.LOW
        "romance", "intimacy" -> DateCostLevel.LOW
        "connection", "play" -> DateCostLevel.FREE
        "seasonal" -> DateCostLevel.LOW
        else -> DateCostLevel.FREE
    }

    internal fun durationForCategory(categoryId: String?): String = when (categoryId?.lowercase()) {
        "adventure", "travel" -> "2–3 hours"
        "food" -> "1–2 hours"
        "learning" -> "1–2 hours"
        "romance", "intimacy" -> "1–2 hours"
        "connection", "play" -> "30 min"
        "seasonal" -> "1–2 hours"
        else -> "30 min"
    }

    internal fun costLabel(costLevel: DateCostLevel): String = when (costLevel) {
        DateCostLevel.FREE -> "free"
        DateCostLevel.LOW -> "low cost"
        DateCostLevel.MEDIUM -> "moderate"
        DateCostLevel.HIGH -> "higher cost"
    }
}

/**
 * Neutral input snapshot for [DateSuggestionEngine].
 *
 * All fields are metadata; no hidden answer text is present. Revealed answers are represented
 * only by boolean flags and category identifiers.
 *
 * @property answeredQuestionsByCategory list of questions answered by the couple; each entry
 *           carries only the category ID and whether it has been revealed
 * @property weeklyRecapCompleted true if the couple completed a weekly recap in this session
 * @property weeklyRecapFavoriteCategory the favorite category ID from the recap, or null
 * @property challengeCompleted true if a challenge was just completed
 * @property completedChallengeCategory category ID of the completed challenge, or null
 * @property partnerSavedDateIdeaIds IDs of date ideas the partner recently saved
 * @property dateIdeaTitleCatalog caller-provided map of idea/category IDs to safe display titles
 * @property dateIdeaCategoryById map of saved date idea IDs to their category IDs
 * @property hasSharedInterestReveal true if a reveal surfaced a shared interest
 * @property sharedInterestCategory category ID of the shared interest, or null
 */
data class DateSuggestionInput(
    val answeredQuestionsByCategory: List<AnsweredQuestionRef> = emptyList(),
    val weeklyRecapCompleted: Boolean = false,
    val weeklyRecapFavoriteCategory: String? = null,
    val challengeCompleted: Boolean = false,
    val completedChallengeCategory: String? = null,
    val partnerSavedDateIdeaIds: List<String> = emptyList(),
    val dateIdeaTitleCatalog: Map<String, String> = emptyMap(),
    val dateIdeaCategoryById: Map<String, String> = emptyMap(),
    val hasSharedInterestReveal: Boolean = false,
    val sharedInterestCategory: String? = null
)

/**
 * A lightweight reference to an answered question.
 *
 * @property categoryId question category used for category-surge detection
 * @property isRevealed true if both partners have already revealed this answer.
 *           The engine never reads answer text, but callers may use this flag to decide
 *           whether revealed-answer metadata can inform a shared-interest suggestion.
 */
data class AnsweredQuestionRef(
    val categoryId: String,
    val isRevealed: Boolean = false
)
