package app.closer.domain

import app.closer.domain.model.DateCostLevel
import app.closer.domain.model.DateSuggestionTrigger
import app.closer.domain.model.SourceFlag
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class DateSuggestionEngineTest {

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

    @Test
    fun `category surge triggered after three answers in same category`() {
        val input = DateSuggestionInput(
            answeredQuestionsByCategory = listOf(
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food")
            )
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        val suggestion = result.first()
        assertEquals(DateSuggestionTrigger.CATEGORY_SURGE, suggestion.triggerReason)
        assertEquals("food", suggestion.categoryId)
        assertTrue(SourceFlag.CATEGORY_METADATA in suggestion.sourceFlags)
        assertEquals(30, suggestion.priority)
        assertTrue(suggestion.suggestedActivity.contains("Cook or taste something new together"))
    }

    @Test
    fun `category surge not triggered with only two answers`() {
        val input = DateSuggestionInput(
            answeredQuestionsByCategory = listOf(
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food")
            )
        )

        val result = DateSuggestionEngine.suggest(input)

        assertTrue(result.isEmpty())
    }

    @Test
    fun `category surge picks highest count category`() {
        val input = DateSuggestionInput(
            answeredQuestionsByCategory = listOf(
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("play"),
                AnsweredQuestionRef("play")
            )
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        assertEquals("food", result.first().categoryId)
    }

    @Test
    fun `category surge uses caller supplied title catalog`() {
        val input = DateSuggestionInput(
            answeredQuestionsByCategory = listOf(
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food")
            ),
            dateIdeaTitleCatalog = mapOf("food" to "Pasta night at home")
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals("Pasta night at home (1–2 hours · moderate)", result.first().suggestedActivity)
    }

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

    @Test
    fun `weekly recap completion triggers suggestion using favorite category`() {
        val input = DateSuggestionInput(
            weeklyRecapCompleted = true,
            weeklyRecapFavoriteCategory = "romance"
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        val suggestion = result.first()
        assertEquals(DateSuggestionTrigger.WEEKLY_RECAP_COMPLETED, suggestion.triggerReason)
        assertEquals("romance", suggestion.categoryId)
        assertEquals(35, suggestion.priority)
        assertTrue(SourceFlag.RECAP_CONTEXT in suggestion.sourceFlags)
        assertTrue(SourceFlag.CATEGORY_METADATA in suggestion.sourceFlags)
        assertTrue(suggestion.suggestedActivity.contains("A quiet, intentional evening together"))
    }

    @Test
    fun `weekly recap completion without favorite category uses fallback`() {
        val input = DateSuggestionInput(
            weeklyRecapCompleted = true,
            weeklyRecapFavoriteCategory = null
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        assertEquals("Plan something together this week (30 min · free)", result.first().suggestedActivity)
    }

    @Test
    fun `weekly recap not completed produces no suggestion`() {
        val input = DateSuggestionInput(
            weeklyRecapCompleted = false,
            weeklyRecapFavoriteCategory = "romance"
        )

        val result = DateSuggestionEngine.suggest(input)

        assertTrue(result.isEmpty())
    }

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

    @Test
    fun `completed challenge triggers celebration suggestion`() {
        val input = DateSuggestionInput(
            challengeCompleted = true,
            completedChallengeCategory = "connection"
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        val suggestion = result.first()
        assertEquals(DateSuggestionTrigger.CHALLENGE_COMPLETED, suggestion.triggerReason)
        assertEquals("connection", suggestion.categoryId)
        assertEquals(40, suggestion.priority)
        assertTrue(SourceFlag.CHALLENGE_CONTEXT in suggestion.sourceFlags)
        assertTrue(suggestion.suggestedActivity.contains("A low-pressure way to check in over something fun"))
    }

    @Test
    fun `incomplete challenge produces no suggestion`() {
        val input = DateSuggestionInput(
            challengeCompleted = false,
            completedChallengeCategory = "connection"
        )

        val result = DateSuggestionEngine.suggest(input)

        assertTrue(result.isEmpty())
    }

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

    @Test
    fun `partner saved date idea triggers suggestion`() {
        val input = DateSuggestionInput(
            partnerSavedDateIdeaIds = listOf("sunset-picnic"),
            dateIdeaTitleCatalog = mapOf("sunset-picnic" to "Sunset picnic in the park"),
            dateIdeaCategoryById = mapOf("sunset-picnic" to "romance")
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        val suggestion = result.first()
        assertEquals(DateSuggestionTrigger.PARTNER_SAVED_DATE_IDEA, suggestion.triggerReason)
        assertEquals("romance", suggestion.categoryId)
        assertEquals(45, suggestion.priority)
        assertTrue(SourceFlag.PARTNER_ACTION in suggestion.sourceFlags)
        assertEquals("Sunset picnic in the park (1–2 hours · low cost)", suggestion.suggestedActivity)
    }

    @Test
    fun `partner saved date idea without catalog uses generic copy`() {
        val input = DateSuggestionInput(
            partnerSavedDateIdeaIds = listOf("unknown-idea")
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        assertEquals(
            "A date idea your partner saved (30 min · free)",
            result.first().suggestedActivity
        )
    }

    @Test
    fun `multiple saved ideas are prioritized with descending priority`() {
        val input = DateSuggestionInput(
            partnerSavedDateIdeaIds = listOf("first", "second", "third"),
            dateIdeaTitleCatalog = mapOf(
                "first" to "First idea",
                "second" to "Second idea",
                "third" to "Third idea"
            )
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(3, result.size)
        assertEquals(45, result[0].priority)
        assertEquals(42, result[1].priority)
        assertEquals(39, result[2].priority)
    }

    // -----------------------------------------------------------------
    // Trigger: shared interest revealed
    // -----------------------------------------------------------------

    @Test
    fun `shared interest reveal triggers high priority suggestion`() {
        val input = DateSuggestionInput(
            hasSharedInterestReveal = true,
            sharedInterestCategory = "travel"
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        val suggestion = result.first()
        assertEquals(DateSuggestionTrigger.SHARED_INTEREST_REVEALED, suggestion.triggerReason)
        assertEquals("travel", suggestion.categoryId)
        assertEquals(50, suggestion.priority)
        assertTrue(suggestion.isFromRevealedAnswer)
        assertTrue(SourceFlag.REVEALED_ANSWER in suggestion.sourceFlags)
        assertTrue(SourceFlag.CATEGORY_METADATA in suggestion.sourceFlags)
        assertTrue(suggestion.suggestedActivity.contains("Plan a mini local adventure"))
    }

    @Test
    fun `shared interest reveal without category uses fallback`() {
        val input = DateSuggestionInput(
            hasSharedInterestReveal = true,
            sharedInterestCategory = null
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        assertEquals(
            "A date idea inspired by something you both revealed (30 min · free)",
            result.first().suggestedActivity
        )
        assertTrue(result.first().isFromRevealedAnswer)
    }

    @Test
    fun `shared interest not revealed produces no suggestion`() {
        val input = DateSuggestionInput(
            hasSharedInterestReveal = false,
            sharedInterestCategory = "travel"
        )

        val result = DateSuggestionEngine.suggest(input)

        assertTrue(result.isEmpty())
    }

    // -----------------------------------------------------------------
    // Combined / priority ordering
    // -----------------------------------------------------------------

    @Test
    fun `all triggers together are sorted by descending priority`() {
        val input = DateSuggestionInput(
            answeredQuestionsByCategory = listOf(
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food")
            ),
            weeklyRecapCompleted = true,
            weeklyRecapFavoriteCategory = "romance",
            challengeCompleted = true,
            completedChallengeCategory = "connection",
            partnerSavedDateIdeaIds = listOf("saved-1"),
            dateIdeaTitleCatalog = mapOf("saved-1" to "Saved idea"),
            hasSharedInterestReveal = true,
            sharedInterestCategory = "travel"
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(5, result.size)
        assertEquals(
            listOf(
                DateSuggestionTrigger.SHARED_INTEREST_REVEALED,
                DateSuggestionTrigger.PARTNER_SAVED_DATE_IDEA,
                DateSuggestionTrigger.CHALLENGE_COMPLETED,
                DateSuggestionTrigger.WEEKLY_RECAP_COMPLETED,
                DateSuggestionTrigger.CATEGORY_SURGE
            ),
            result.map { it.triggerReason }
        )
    }

    @Test
    fun `empty input produces no suggestions`() {
        val input = DateSuggestionInput()

        val result = DateSuggestionEngine.suggest(input)

        assertTrue(result.isEmpty())
    }

    // -----------------------------------------------------------------
    // Privacy / safety
    // -----------------------------------------------------------------

    @Test
    fun `suggestions never include hidden answer text`() {
        val input = DateSuggestionInput(
            answeredQuestionsByCategory = listOf(
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food"),
                AnsweredQuestionRef("food")
            )
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        assertFalse(result.first().suggestedActivity.contains("hidden"))
        assertFalse(result.first().suggestedActivity.contains("private"))
    }

    @Test
    fun `shared interest suggestion is marked as revealed source only`() {
        val input = DateSuggestionInput(
            hasSharedInterestReveal = true,
            sharedInterestCategory = "romance"
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        assertTrue(result.first().isFromRevealedAnswer)
        assertTrue(SourceFlag.REVEALED_ANSWER in result.first().sourceFlags)
    }

    @Test
    fun `category surge suggestions are not marked as revealed`() {
        val input = DateSuggestionInput(
            answeredQuestionsByCategory = listOf(
                AnsweredQuestionRef("food", isRevealed = true),
                AnsweredQuestionRef("food", isRevealed = true),
                AnsweredQuestionRef("food", isRevealed = true)
            )
        )

        val result = DateSuggestionEngine.suggest(input)

        assertEquals(1, result.size)
        assertFalse(result.first().isFromRevealedAnswer)
        assertFalse(SourceFlag.REVEALED_ANSWER in result.first().sourceFlags)
    }

    // -----------------------------------------------------------------
    // Helper correctness
    // -----------------------------------------------------------------

    @Test
    fun `cost level mapping covers known categories`() {
        assertEquals(DateCostLevel.LOW, DateSuggestionEngine.costLevelForCategory("adventure"))
        assertEquals(DateCostLevel.MEDIUM, DateSuggestionEngine.costLevelForCategory("food"))
        assertEquals(DateCostLevel.FREE, DateSuggestionEngine.costLevelForCategory("play"))
        assertEquals(DateCostLevel.FREE, DateSuggestionEngine.costLevelForCategory(null))
        assertEquals(DateCostLevel.FREE, DateSuggestionEngine.costLevelForCategory("unknown"))
    }

    @Test
    fun `duration mapping covers known categories`() {
        assertEquals("2–3 hours", DateSuggestionEngine.durationForCategory("travel"))
        assertEquals("30 min", DateSuggestionEngine.durationForCategory("play"))
        assertEquals("30 min", DateSuggestionEngine.durationForCategory(null))
    }

    @Test
    fun `cost labels are human readable`() {
        assertEquals("free", DateSuggestionEngine.costLabel(DateCostLevel.FREE))
        assertEquals("low cost", DateSuggestionEngine.costLabel(DateCostLevel.LOW))
        assertEquals("moderate", DateSuggestionEngine.costLabel(DateCostLevel.MEDIUM))
        assertEquals("higher cost", DateSuggestionEngine.costLabel(DateCostLevel.HIGH))
    }
}
