package app.closer.domain

import app.closer.domain.model.MemoryCapsuleSource
import app.closer.domain.model.MemoryChallengeEvent
import app.closer.domain.model.MemoryDateIdeaEvent
import app.closer.domain.model.MemoryManualInput
import app.closer.domain.model.MemoryRecapEvent
import app.closer.domain.model.MemoryRevealEvent
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 MemoryCapsuleGeneratorTest {

    private val coupleId = "couple_1"
    private val authorId = "user_1"
    private val today: LocalDate = LocalDate.of(2026, 6, 19)

    // -----------------------------------------------------------------
    // Date idea → memory
    // -----------------------------------------------------------------

    @Test
    fun `date idea produces memory with category title`() {
        val event = MemoryDateIdeaEvent(
            dateIdeaId = "idea_1",
            title = "Pasta night at home",
            category = "food",
            scheduledDate = today
        )

        val capsule = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today)

        assertEquals(coupleId, capsule.coupleId)
        assertEquals(authorId, capsule.authorId)
        assertEquals("Our Food date: Pasta night at home", capsule.title)
        assertEquals(today, capsule.date)
        assertEquals("food", capsule.category)
        assertEquals(MemoryCapsuleSource.DATE_IDEA, capsule.source)
        assertEquals("idea_1", capsule.sourceRefId)
        assertTrue(capsule.tags.contains("food"))
        assertTrue(capsule.tags.contains("date-idea"))
        assertNull(capsule.noteCiphertext)
        assertNull(capsule.photoStoragePath)
    }

    @Test
    fun `date idea without scheduled date uses now`() {
        val event = MemoryDateIdeaEvent(
            dateIdeaId = "idea_2",
            title = "Sunset hike",
            category = "adventure"
        )

        val capsule = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today)

        assertEquals(today, capsule.date)
        assertTrue(capsule.tags.contains("adventure"))
    }

    @Test
    fun `date idea without category defaults to date category`() {
        val event = MemoryDateIdeaEvent(dateIdeaId = "idea_3", title = "Movie night")

        val capsule = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today)

        assertEquals("date", capsule.category)
        assertEquals("Our Date date: Movie night", capsule.title)
    }

    @Test
    fun `date idea accepts encrypted note and photo path`() {
        val event = MemoryDateIdeaEvent(
            dateIdeaId = "idea_4",
            title = "Farmers market brunch",
            category = "food",
            scheduledDate = today
        )

        val capsule = MemoryCapsuleGenerator.fromDateIdea(
            coupleId,
            authorId,
            event,
            noteCiphertext = "enc:v1:abc123",
            photoStoragePath = "couples/couple_1/memories/photo_1.jpg",
            now = today
        )

        assertEquals("enc:v1:abc123", capsule.noteCiphertext)
        assertEquals("couples/couple_1/memories/photo_1.jpg", capsule.photoStoragePath)
    }

    // -----------------------------------------------------------------
    // Reveal → memory
    // -----------------------------------------------------------------

    @Test
    fun `reveal produces memory without answer text in title`() {
        val event = MemoryRevealEvent(
            questionId = "q_1",
            categoryId = "intimacy",
            revealedDate = today
        )

        val capsule = MemoryCapsuleGenerator.fromReveal(coupleId, authorId, event)

        assertEquals("A moment we revealed together in Intimacy", capsule.title)
        assertEquals("intimacy", capsule.category)
        assertEquals(today, capsule.date)
        assertEquals(MemoryCapsuleSource.REVEAL, capsule.source)
        assertEquals("q_1", capsule.sourceRefId)
        assertFalse(capsule.title.contains("secret"))
        assertFalse(capsule.title.contains("password"))
        assertFalse(capsule.tags.any { it.contains("secret") })
    }

    @Test
    fun `reveal without category uses reveal category`() {
        val event = MemoryRevealEvent(
            questionId = "q_2",
            revealedDate = today
        )

        val capsule = MemoryCapsuleGenerator.fromReveal(coupleId, authorId, event)

        assertEquals("reveal", capsule.category)
        assertEquals("A moment we revealed together", capsule.title)
    }

    // -----------------------------------------------------------------
    // Weekly recap → memory
    // -----------------------------------------------------------------

    @Test
    fun `weekly recap produces memory with week number`() {
        val weekStart = LocalDate.of(2026, 6, 14)
        val weekEnd = weekStart.plusDays(6)
        val event = MemoryRecapEvent(
            recapId = "recap_1",
            weekStart = weekStart,
            weekEnd = weekEnd,
            favoriteCategory = "play"
        )

        val capsule = MemoryCapsuleGenerator.fromWeeklyRecap(coupleId, authorId, event)

        assertEquals("Week 25 together in Play", capsule.title)
        assertEquals("play", capsule.category)
        assertEquals(weekEnd, capsule.date)
        assertEquals(MemoryCapsuleSource.WEEKLY_RECAP, capsule.source)
        assertEquals("recap_1", capsule.sourceRefId)
        assertTrue(capsule.tags.contains("play"))
        assertTrue(capsule.tags.contains("weekly-recap"))
        assertTrue(capsule.tags.contains("week-25"))
    }

    @Test
    fun `weekly recap without favorite category uses recap category`() {
        val weekStart = LocalDate.of(2026, 6, 14)
        val weekEnd = weekStart.plusDays(6)
        val event = MemoryRecapEvent(
            recapId = "recap_2",
            weekStart = weekStart,
            weekEnd = weekEnd
        )

        val capsule = MemoryCapsuleGenerator.fromWeeklyRecap(coupleId, authorId, event)

        assertEquals("recap", capsule.category)
        assertEquals("Week 25 together", capsule.title)
    }

    // -----------------------------------------------------------------
    // Challenge → memory
    // -----------------------------------------------------------------

    @Test
    fun `challenge completion produces memory`() {
        val event = MemoryChallengeEvent(
            challengeId = "challenge_1",
            challengeCategory = "connection",
            completedDate = today
        )

        val capsule = MemoryCapsuleGenerator.fromChallenge(coupleId, authorId, event)

        assertEquals("Challenge completed in Connection", capsule.title)
        assertEquals("connection", capsule.category)
        assertEquals(today, capsule.date)
        assertEquals(MemoryCapsuleSource.CHALLENGE, capsule.source)
        assertEquals("challenge_1", capsule.sourceRefId)
    }

    @Test
    fun `challenge without category uses challenge category`() {
        val event = MemoryChallengeEvent(
            challengeId = "challenge_2",
            completedDate = today
        )

        val capsule = MemoryCapsuleGenerator.fromChallenge(coupleId, authorId, event)

        assertEquals("challenge", capsule.category)
        assertEquals("Challenge completed", capsule.title)
    }

    // -----------------------------------------------------------------
    // Manual memory
    // -----------------------------------------------------------------

    @Test
    fun `manual input produces memory with encrypted note`() {
        val input = MemoryManualInput(
            title = "Our first road trip",
            date = today,
            category = "travel",
            noteCiphertext = "enc:v1:note_secret",
            photoStoragePath = "photos/roadtrip.jpg"
        )

        val capsule = MemoryCapsuleGenerator.fromManualInput(coupleId, authorId, input, now = today)

        assertEquals("Our first road trip", capsule.title)
        assertEquals("travel", capsule.category)
        assertEquals(today, capsule.date)
        assertEquals("enc:v1:note_secret", capsule.noteCiphertext)
        assertEquals("photos/roadtrip.jpg", capsule.photoStoragePath)
        assertEquals(MemoryCapsuleSource.MANUAL, capsule.source)
        assertTrue(capsule.tags.contains("travel"))
        assertTrue(capsule.tags.contains("manual"))
    }

    @Test
    fun `manual input with blank title uses fallback date title`() {
        val input = MemoryManualInput(
            title = "",
            date = LocalDate.of(2026, 12, 25),
            category = "romance"
        )

        val capsule = MemoryCapsuleGenerator.fromManualInput(coupleId, authorId, input, now = today)

        assertEquals("A moment from December 25", capsule.title)
        assertEquals("romance", capsule.category)
    }

    @Test
    fun `manual input with blank category defaults to memory category`() {
        val input = MemoryManualInput(
            title = "Random note",
            date = today,
            category = ""
        )

        val capsule = MemoryCapsuleGenerator.fromManualInput(coupleId, authorId, input, now = today)

        assertEquals("memory", capsule.category)
    }

    // -----------------------------------------------------------------
    // Tags
    // -----------------------------------------------------------------

    @Test
    fun `tags include category source month season and weekday`() {
        val tags = MemoryCapsuleGenerator.buildTags(
            category = "food",
            source = MemoryCapsuleSource.DATE_IDEA,
            date = today,
            now = today
        )

        assertTrue(tags.contains("food"))
        assertTrue(tags.contains("date-idea"))
        assertTrue(tags.contains("june"))
        assertTrue(tags.contains("summer"))
        assertTrue(tags.contains("friday"))
        assertTrue(tags.contains("this-year"))
    }

    @Test
    fun `tags are distinct and lowercase`() {
        val tags = MemoryCapsuleGenerator.buildTags(
            category = "food",
            source = MemoryCapsuleSource.MANUAL,
            date = today,
            extras = listOf("Food", "food", "manual"),
            now = today
        )

        assertEquals(tags.size, tags.toSet().size)
        assertTrue(tags.all { it == it.lowercase() })
    }

    @Test
    fun `tags handle multi word category`() {
        val tags = MemoryCapsuleGenerator.buildTags(
            category = "road trip",
            source = MemoryCapsuleSource.MANUAL,
            date = today,
            now = today
        )

        assertTrue(tags.contains("road-trip"))
    }

    @Test
    fun `extras are normalized and deduplicated`() {
        val tags = MemoryCapsuleGenerator.buildTags(
            category = "food",
            source = MemoryCapsuleSource.DATE_IDEA,
            date = today,
            extras = listOf("Week 25", "week-25", "Food"),
            now = today
        )

        assertEquals(1, tags.count { it == "week-25" })
        assertTrue(tags.contains("week-25"))
        assertFalse(tags.contains("week 25"))
        assertFalse(tags.contains("Food"))
    }

    // -----------------------------------------------------------------
    // Helpers
    // -----------------------------------------------------------------

    @Test
    fun `categoryFragment omits generic categories`() {
        assertEquals("", MemoryCapsuleGenerator.categoryFragment("date"))
        assertEquals("", MemoryCapsuleGenerator.categoryFragment("reveal"))
        assertEquals("", MemoryCapsuleGenerator.categoryFragment("recap"))
        assertEquals("", MemoryCapsuleGenerator.categoryFragment("challenge"))
        assertEquals("", MemoryCapsuleGenerator.categoryFragment("memory"))
        assertEquals(" in Romance", MemoryCapsuleGenerator.categoryFragment("romance"))
    }

    @Test
    fun `displayTitleForCategory returns known category display name`() {
        assertEquals("Adventure", MemoryCapsuleGenerator.displayTitleForCategory("adventure"))
        assertEquals("Intimacy", MemoryCapsuleGenerator.displayTitleForCategory("intimacy"))
        assertEquals("Custom", MemoryCapsuleGenerator.displayTitleForCategory("custom"))
    }

    @Test
    fun `weekNumberLabel returns ISO week number`() {
        val weekStart = LocalDate.of(2026, 6, 14)
        val weekEnd = weekStart.plusDays(6)

        assertEquals("25", MemoryCapsuleGenerator.weekNumberLabel(weekStart, weekEnd))
    }

    @Test
    fun `weekNumberLabel returns null when inputs are null`() {
        assertNull(MemoryCapsuleGenerator.weekNumberLabel(null, today))
        assertNull(MemoryCapsuleGenerator.weekNumberLabel(today, null))
    }

    @Test
    fun `seasonForMonth returns correct season`() {
        assertEquals("winter", MemoryCapsuleGenerator.seasonForMonth(12))
        assertEquals("winter", MemoryCapsuleGenerator.seasonForMonth(1))
        assertEquals("spring", MemoryCapsuleGenerator.seasonForMonth(4))
        assertEquals("summer", MemoryCapsuleGenerator.seasonForMonth(6))
        assertEquals("fall", MemoryCapsuleGenerator.seasonForMonth(10))
    }

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

    @Test
    fun `generated titles never contain decrypted answer text`() {
        val revealEvent = MemoryRevealEvent(
            questionId = "q_secret",
            categoryId = "intimacy",
            revealedDate = today
        )
        val revealCapsule = MemoryCapsuleGenerator.fromReveal(coupleId, authorId, revealEvent)

        val recapEvent = MemoryRecapEvent(
            recapId = "recap_secret",
            weekStart = today.minusDays(6),
            weekEnd = today,
            favoriteCategory = "intimacy"
        )
        val recapCapsule = MemoryCapsuleGenerator.fromWeeklyRecap(coupleId, authorId, recapEvent)

        val sensitiveValues = listOf("I love you", "my secret", "private answer", "plaintext")
        listOf(revealCapsule, recapCapsule).forEach { capsule ->
            sensitiveValues.forEach { value ->
                assertFalse(capsule.title.contains(value))
                assertFalse(capsule.tags.any { it.contains(value.lowercase().replace(" ", "-")) })
            }
        }
    }

    @Test
    fun `manual input generator does not mutate encrypted note`() {
        val input = MemoryManualInput(
            title = "Private thought",
            date = today,
            category = "intimacy",
            noteCiphertext = "enc:v1:do-not-read"
        )

        val capsule = MemoryCapsuleGenerator.fromManualInput(coupleId, authorId, input, now = today)

        assertEquals("enc:v1:do-not-read", capsule.noteCiphertext)
    }

    // -----------------------------------------------------------------
    // Determinism
    // -----------------------------------------------------------------

    @Test
    fun `same input produces identical capsules`() {
        val event = MemoryDateIdeaEvent(
            dateIdeaId = "idea_5",
            title = "Board game cafe",
            category = "play",
            scheduledDate = today
        )

        val first = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today)
        val second = MemoryCapsuleGenerator.fromDateIdea(coupleId, authorId, event, now = today)

        assertEquals(first, second)
    }
}
