package app.closer.domain

import app.closer.domain.model.QuestionPack
import app.closer.domain.model.WeeklyRecapAnswer
import app.closer.domain.model.WeeklyRecapCapsule
import app.closer.domain.model.WeeklyRecapChallengeDay
import app.closer.domain.model.WeeklyRecapDateIdea
import app.closer.domain.model.WeeklyRecapGame
import app.closer.domain.model.WeeklyRecapInput
import app.closer.domain.model.WeeklyRecapReveal
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.time.LocalDate

class WeeklyRecapGeneratorTest {

    private val sunday: LocalDate = LocalDate.of(2026, 6, 14)
    private val saturday: LocalDate = sunday.plusDays(6)

    private fun baseInput(
        weekStart: LocalDate = sunday,
        weekEnd: LocalDate = saturday,
        answers: List<WeeklyRecapAnswer> = emptyList(),
        reveals: List<WeeklyRecapReveal> = emptyList(),
        games: List<WeeklyRecapGame> = emptyList(),
        challenges: List<WeeklyRecapChallengeDay> = emptyList(),
        dates: List<WeeklyRecapDateIdea> = emptyList(),
        capsules: List<WeeklyRecapCapsule> = emptyList(),
        packs: List<QuestionPack> = emptyList(),
        categoryTitles: Map<String, String> = emptyMap()
    ) = WeeklyRecapInput(
        weekStart = weekStart,
        weekEnd = weekEnd,
        answers = answers,
        reveals = reveals,
        gamesCompleted = games,
        challengeProgress = challenges,
        dateIdeasSaved = dates,
        capsules = capsules,
        availablePacks = packs,
        categoryTitles = categoryTitles
    )

    // -----------------------------------------------------------------
    // Basic counts
    // -----------------------------------------------------------------

    @Test
    fun `empty input produces zero counts and no category`() {
        val input = baseInput()

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(0, recap.questionsAnswered)
        assertEquals(0, recap.revealsOpened)
        assertEquals(0, recap.gamesCompleted)
        assertEquals(0, recap.challengeProgressDays)
        assertNull(recap.favoriteCategory)
        assertNull(recap.favoriteCategoryDisplay)
        assertFalse(recap.dateIdeaSaved)
        assertFalse(recap.memoryCapsuleCreated)
        assertFalse(recap.memoryCapsuleUnlocked)
        assertNull(recap.suggestedNextPack)
        assertEquals("Choose next week's pack", recap.ctaCopy)
    }

    @Test
    fun `questions answered are counted for the week`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1)),
                WeeklyRecapAnswer("u2", "q1", "intimacy", sunday.plusDays(1)),
                WeeklyRecapAnswer("u1", "q2", "play", sunday.plusDays(3))
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(3, recap.questionsAnswered)
    }

    @Test
    fun `questions outside the week are ignored`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.minusDays(1)),
                WeeklyRecapAnswer("u1", "q2", "play", saturday.plusDays(1)),
                WeeklyRecapAnswer("u2", "q3", "intimacy", sunday.plusDays(2))
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(1, recap.questionsAnswered)
    }

    @Test
    fun `reveals and games are counted`() {
        val input = baseInput(
            reveals = listOf(
                WeeklyRecapReveal(sunday.plusDays(2)),
                WeeklyRecapReveal(sunday.plusDays(4)),
                WeeklyRecapReveal(sunday.plusDays(6))
            ),
            games = listOf(
                WeeklyRecapGame(sunday.plusDays(1), "wheel"),
                WeeklyRecapGame(sunday.plusDays(5), "this_or_that")
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(3, recap.revealsOpened)
        assertEquals(2, recap.gamesCompleted)
    }

    @Test
    fun `challenge progress counts unique days not duplicate completions`() {
        val input = baseInput(
            challenges = listOf(
                WeeklyRecapChallengeDay("c1", 1, sunday.plusDays(1)),
                WeeklyRecapChallengeDay("c1", 1, sunday.plusDays(1)), // duplicate
                WeeklyRecapChallengeDay("c1", 2, sunday.plusDays(2)),
                WeeklyRecapChallengeDay("c2", 1, sunday.plusDays(3))
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(3, recap.challengeProgressDays)
    }

    // -----------------------------------------------------------------
    // Privacy
    // -----------------------------------------------------------------

    @Test
    fun `hidden written answers do not count toward questions answered`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1), isRevealed = false, hasText = true),
                WeeklyRecapAnswer("u2", "q1", "intimacy", sunday.plusDays(1), isRevealed = true, hasText = true),
                WeeklyRecapAnswer("u1", "q2", "play", sunday.plusDays(2), isRevealed = false, hasText = false)
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(2, recap.questionsAnswered)
    }

    @Test
    fun `revealed written answers count normally`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1), isRevealed = true, hasText = true),
                WeeklyRecapAnswer("u2", "q1", "intimacy", sunday.plusDays(1), isRevealed = true, hasText = true)
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(2, recap.questionsAnswered)
    }

    @Test
    fun `answer text is never surfaced in the recap output`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1), isRevealed = true, hasText = true)
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertFalse(recap.bodyCopy.contains("secret"))
        assertFalse(recap.bodyCopy.contains("password"))
        assertFalse(recap.bodyCopy.contains("answer text"))
    }

    // -----------------------------------------------------------------
    // Date ideas and memory capsules
    // -----------------------------------------------------------------

    @Test
    fun `date idea saved flag is set when a date idea event exists`() {
        val input = baseInput(
            dates = listOf(WeeklyRecapDateIdea(sunday.plusDays(2)))
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertTrue(recap.dateIdeaSaved)
    }

    @Test
    fun `memory capsule created and unlocked flags are tracked separately`() {
        val input = baseInput(
            capsules = listOf(
                WeeklyRecapCapsule(sunday.plusDays(1), isUnlock = false),
                WeeklyRecapCapsule(sunday.plusDays(5), isUnlock = true)
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertTrue(recap.memoryCapsuleCreated)
        assertTrue(recap.memoryCapsuleUnlocked)
    }

    // -----------------------------------------------------------------
    // Favorite category
    // -----------------------------------------------------------------

    @Test
    fun `favorite category is the category with the most answers`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1)),
                WeeklyRecapAnswer("u2", "q1", "intimacy", sunday.plusDays(1)),
                WeeklyRecapAnswer("u1", "q2", "play", sunday.plusDays(2)),
                WeeklyRecapAnswer("u2", "q3", "intimacy", sunday.plusDays(3))
            ),
            categoryTitles = mapOf("intimacy" to "Intimacy", "play" to "Play")
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals("intimacy", recap.favoriteCategory)
        assertEquals("Intimacy", recap.favoriteCategoryDisplay)
    }

    @Test
    fun `favorite category display falls back to category id when title is missing`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1))
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals("intimacy", recap.favoriteCategoryDisplay)
    }

    // -----------------------------------------------------------------
    // Suggested next pack
    // -----------------------------------------------------------------

    @Test
    fun `suggested pack prefers a pack matching the favorite category`() {
        val intimacyPack = QuestionPack(
            id = "pack_intimacy",
            displayName = "Deeper Intimacy",
            description = "",
            isPremium = true,
            categoryIds = listOf("intimacy")
        )
        val playPack = QuestionPack(
            id = "pack_play",
            displayName = "Playful Nights",
            description = "",
            isPremium = false,
            categoryIds = listOf("play")
        )
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1)),
                WeeklyRecapAnswer("u2", "q1", "intimacy", sunday.plusDays(1))
            ),
            packs = listOf(intimacyPack, playPack)
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(intimacyPack, recap.suggestedNextPack)
    }

    @Test
    fun `suggested pack falls back to least used category when favorite is unavailable`() {
        val adventurePack = QuestionPack(
            id = "pack_adventure",
            displayName = "Adventure Together",
            description = "",
            isPremium = false,
            categoryIds = listOf("adventure")
        )
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1))
            ),
            packs = listOf(adventurePack)
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(adventurePack, recap.suggestedNextPack)
    }

    @Test
    fun `suggested pack is null when no packs are available`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1))
            ),
            packs = emptyList()
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertNull(recap.suggestedNextPack)
    }

    // -----------------------------------------------------------------
    // Copy
    // -----------------------------------------------------------------

    @Test
    fun `body copy uses singular and plural phrases correctly`() {
        val input = baseInput(
            answers = listOf(WeeklyRecapAnswer("u1", "q1", null, sunday.plusDays(1))),
            reveals = listOf(WeeklyRecapReveal(sunday.plusDays(1))),
            games = listOf(
                WeeklyRecapGame(sunday.plusDays(1)),
                WeeklyRecapGame(sunday.plusDays(2))
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertTrue(recap.bodyCopy.contains("You answered 1 question"))
        assertTrue(recap.bodyCopy.contains("opened 1 reveal"))
        assertTrue(recap.bodyCopy.contains("completed 2 games"))
    }

    @Test
    fun `body copy includes favorite category and rhythm when available`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1)),
                WeeklyRecapAnswer("u2", "q2", "intimacy", sunday.plusDays(1)),
                WeeklyRecapAnswer("u1", "q3", "intimacy", sunday.plusDays(3))
            ),
            categoryTitles = mapOf("intimacy" to "Intimacy")
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertTrue(recap.bodyCopy.contains("You answered 3 questions"))
        assertTrue(recap.bodyCopy.contains("You spent the most time in Intimacy."))
        assertNotNull(recap.strongestRhythmCopy)
        assertTrue(recap.strongestRhythmCopy!!.startsWith("Your strongest rhythm"))
    }

    @Test
    fun `strongest rhythm is null with fewer than two answers`() {
        val input = baseInput(
            answers = listOf(
                WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1))
            )
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertNull(recap.strongestRhythmCopy)
    }

    @Test
    fun `body copy is warm and does not include zero counts awkwardly`() {
        val input = baseInput()

        val recap = WeeklyRecapGenerator.generate(input)

        assertTrue(recap.bodyCopy.contains("You answered no questions"))
        assertTrue(recap.bodyCopy.contains("opened no reveals"))
        assertTrue(recap.bodyCopy.contains("completed no games"))
    }

    // -----------------------------------------------------------------
    // Determinism and partial data
    // -----------------------------------------------------------------

    @Test
    fun `recap is deterministic for unsorted input lists`() {
        val answers = listOf(
            WeeklyRecapAnswer("u1", "q3", "play", sunday.plusDays(3)),
            WeeklyRecapAnswer("u1", "q1", "intimacy", sunday.plusDays(1)),
            WeeklyRecapAnswer("u2", "q2", "intimacy", sunday.plusDays(2))
        )
        val input = baseInput(answers = answers)

        val first = WeeklyRecapGenerator.generate(input)
        val second = WeeklyRecapGenerator.generate(input)

        assertEquals(first, second)
        assertEquals("intimacy", first.favoriteCategory)
    }

    @Test
    fun `partial data produces valid recap without errors`() {
        val input = baseInput(
            answers = listOf(WeeklyRecapAnswer("u1", "q1", null, sunday.plusDays(1))),
            dates = listOf(WeeklyRecapDateIdea(sunday.plusDays(5)))
        )

        val recap = WeeklyRecapGenerator.generate(input)

        assertEquals(1, recap.questionsAnswered)
        assertTrue(recap.dateIdeaSaved)
        assertEquals(0, recap.gamesCompleted)
        assertNull(recap.favoriteCategory)
    }
}
