package app.closer.domain

import app.closer.domain.model.ChallengeDayPrompt
import app.closer.domain.model.ChallengeProgressState
import app.closer.domain.model.ChallengeStateInput
import app.closer.domain.model.ChallengeStatus
import app.closer.domain.model.ConnectionChallenge
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import java.time.LocalDate

class ChallengeStateMachineTest {

    private val today: LocalDate = LocalDate.of(2026, 6, 19)
    private val yesterday = today.minusDays(1)

    private fun challenge(
        id: String = "gratitude-week",
        title: String = "Gratitude Week",
        durationDays: Int = 7
    ) = ConnectionChallenge(
        id = id,
        title = title,
        description = "Seven days of small thank-yous.",
        emoji = "🌱",
        category = "connection",
        durationDays = durationDays,
        days = (1..durationDays).map { day ->
            ChallengeDayPrompt(day = day, prompt = "Day $day prompt", hint = "hint $day")
        }
    )

    private fun progress(
        challengeId: String = "gratitude-week",
        startedAt: Long = 0L,
        status: String = "active",
        mine: List<Int> = emptyList(),
        partner: List<Int> = emptyList()
    ) = ChallengeProgressState(
        challengeId = challengeId,
        startedAt = startedAt,
        status = status,
        myCompletedDays = mine,
        partnerCompletedDays = partner
    )

    // -----------------------------------------------------------------
    // Not started
    // -----------------------------------------------------------------

    @Test
    fun `not started when no progress and no start date`() {
        val input = ChallengeStateInput(
            challenge = challenge(),
            progress = progress(),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(ChallengeStatus.NOT_STARTED, state.state)
        assertEquals("Your 7-day challenge is ready — do today's step and mark it done.", state.copy)
        assertEquals("I did it today", state.cta)
        assertTrue(state.canAdvance)
    }

    // -----------------------------------------------------------------
    // Started today
    // -----------------------------------------------------------------

    @Test
    fun `started today when partner completed but user has not`() {
        val input = ChallengeStateInput(
            challenge = challenge(),
            progress = progress(partner = listOf(1)),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(ChallengeStatus.STARTED_TODAY, state.state)
        assertEquals("Your partner completed today — now it's your turn.", state.copy)
        assertEquals("I did it today", state.cta)
        assertTrue(state.canAdvance)
    }

    // -----------------------------------------------------------------
    // Waiting for partner
    // -----------------------------------------------------------------

    @Test
    fun `waiting for partner when user completed but partner has not`() {
        val input = ChallengeStateInput(
            challenge = challenge(),
            progress = progress(mine = listOf(1)),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(ChallengeStatus.WAITING_FOR_PARTNER, state.state)
        assertEquals("You did your part today. Waiting for your partner.", state.copy)
        assertEquals(null, state.cta)
        assertFalse(state.canAdvance)
    }

    // -----------------------------------------------------------------
    // Both completed today
    // -----------------------------------------------------------------

    @Test
    fun `both completed today when both partners finished current day`() {
        val input = ChallengeStateInput(
            challenge = challenge(),
            progress = progress(mine = listOf(1), partner = listOf(1)),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(ChallengeStatus.BOTH_COMPLETED_TODAY, state.state)
        assertEquals("Both of you showed up today.", state.copy)
        assertEquals("See tomorrow's step", state.cta)
        assertFalse(state.canAdvance)
        assertEquals(listOf(1), state.jointCompletedDays)
    }

    @Test
    fun `both completed today on final day marks challenge complete`() {
        val input = ChallengeStateInput(
            challenge = challenge(durationDays = 3),
            progress = progress(
                mine = listOf(1, 2, 3),
                partner = listOf(1, 2, 3)
            ),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(ChallengeStatus.CHALLENGE_COMPLETE, state.state)
        assertEquals("🏅", state.badge)
        assertNull(state.cta)
        assertFalse(state.canAdvance)
    }

    // -----------------------------------------------------------------
    // Day missed
    // -----------------------------------------------------------------

    @Test
    fun `day missed when neither partner completed expected day and break mode enabled`() {
        val startedAt = yesterday.toEpochMillis()
        val input = ChallengeStateInput(
            challenge = challenge(),
            progress = progress(startedAt = startedAt),
            today = today,
            breakOnMissedDay = true
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(ChallengeStatus.DAY_MISSED, state.state)
        assertEquals("No shame. Pick it back up tonight.", state.copy)
        assertEquals("Pick it back up", state.cta)
        assertEquals(yesterday, state.missedDate)
    }

    @Test
    fun `day missed does not break challenge by default`() {
        val startedAt = yesterday.toEpochMillis()
        val input = ChallengeStateInput(
            challenge = challenge(),
            progress = progress(startedAt = startedAt),
            today = today,
            breakOnMissedDay = false
        )

        val state = ChallengeStateMachine.compute(input)

        // Without break-on-miss the challenge remains actionable.
        assertEquals(ChallengeStatus.DAY_MISSED, state.state)
        assertTrue(state.canAdvance)
    }

    @Test
    fun `first day not flagged as missed on same day as start`() {
        val startedAt = today.toEpochMillis()
        val input = ChallengeStateInput(
            challenge = challenge(),
            progress = progress(startedAt = startedAt),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertNull(state.missedDate)
        assertEquals(ChallengeStatus.NOT_STARTED, state.state)
    }

    // -----------------------------------------------------------------
    // Challenge complete
    // -----------------------------------------------------------------

    @Test
    fun `explicit completion produces challenge complete state`() {
        val input = ChallengeStateInput(
            challenge = challenge(durationDays = 3),
            progress = progress(
                status = "completed",
                mine = listOf(1, 2, 3),
                partner = listOf(1, 2)
            ),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(ChallengeStatus.CHALLENGE_COMPLETE, state.state)
        assertTrue(state.isComplete)
        assertEquals("🏅", state.badge)
        assertNull(state.cta)
        assertFalse(state.canAdvance)
    }

    @Test
    fun `all days jointly complete marks challenge complete`() {
        val input = ChallengeStateInput(
            challenge = challenge(durationDays = 2),
            progress = progress(
                mine = listOf(1, 2),
                partner = listOf(1, 2)
            ),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(ChallengeStatus.CHALLENGE_COMPLETE, state.state)
        assertEquals(listOf(1, 2), state.jointCompletedDays)
    }

    // -----------------------------------------------------------------
    // Multi-day progress
    // -----------------------------------------------------------------

    @Test
    fun `current day advances based on user progress`() {
        val input = ChallengeStateInput(
            challenge = challenge(),
            progress = progress(mine = listOf(1, 2)),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(3, state.currentDay)
        assertEquals(ChallengeStatus.WAITING_FOR_PARTNER, state.state)
    }

    @Test
    fun `current day capped at total days`() {
        val input = ChallengeStateInput(
            challenge = challenge(durationDays = 3),
            progress = progress(mine = listOf(1, 2, 3)),
            today = today
        )

        val state = ChallengeStateMachine.compute(input)

        assertEquals(3, state.currentDay)
    }

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

    private fun LocalDate.toEpochMillis(): Long =
        java.time.ZoneId.systemDefault().let { zone ->
            this.atStartOfDay(zone).toInstant().toEpochMilli()
        }
}
