package com.ponzischeme89.memby.data

import com.ponzischeme89.memby.data.model.BaseItem
import com.ponzischeme89.memby.data.model.UserItemData
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

/**
 * Deliberately parallel to the gateway's `continue_watching_test.go`. Continue Watching
 * must hold the same cards in the same order whether or not the container is up.
 */
class ContinueWatchingTest {
    private fun resume(id: String, seriesId: String? = null, playedAt: String? = null) =
        BaseItem(
            id = id,
            seriesId = seriesId,
            userData = UserItemData(lastPlayedDate = playedAt),
        )

    private fun upNext(id: String, seriesId: String) =
        BaseItem(id = id, seriesId = seriesId, userData = UserItemData())

    /**
     * The case the merged row exists for: an episode was just finished, so it has left the
     * resume list, and the next one must be the first card rather than sitting in a second
     * row further down the launcher.
     */
    @Test
    fun leadsWithTheJustFinishedShow() {
        val merged = mergeContinueWatching(
            resume = listOf(resume("film", playedAt = "2026-08-01T20:00:00.0000000Z")),
            nextUp = listOf(upNext("s2e5", "severance")),
            seriesLastPlayed = mapOf("severance" to "2026-08-06T21:00:00.0000000Z"),
        )

        assertEquals(listOf("s2e5", "film"), merged.map(BaseItem::id))
    }

    /** A show being watched right now appears once, as the episode it is part-way through. */
    @Test
    fun prefersTheResumeEpisodeOfASeries() {
        val merged = mergeContinueWatching(
            resume = listOf(resume("s1e3", "show", "2026-08-06T19:00:00Z")),
            nextUp = listOf(upNext("s1e4", "show"), upNext("other-e1", "other")),
            seriesLastPlayed = mapOf(
                "show" to "2026-08-06T19:00:00Z",
                "other" to "2026-08-02T19:00:00Z",
            ),
        )

        assertEquals(listOf("s1e3", "other-e1"), merged.map(BaseItem::id))
    }

    /**
     * Each source keeps its own order. Emby's ordering within a list is the useful part of
     * both, so this is a merge of two sorted lists and never a sort of their union.
     */
    @Test
    fun keepsEachSourceOrder() {
        val merged = mergeContinueWatching(
            resume = listOf(
                resume("r1", playedAt = "2026-08-06T10:00:00Z"),
                resume("r2", playedAt = "2026-08-04T10:00:00Z"),
                resume("r3", playedAt = "2026-08-01T10:00:00Z"),
            ),
            nextUp = listOf(upNext("n1", "alpha"), upNext("n2", "beta")),
            seriesLastPlayed = mapOf(
                "alpha" to "2026-08-05T10:00:00Z",
                "beta" to "2026-08-03T10:00:00Z",
            ),
        )

        assertEquals(listOf("r1", "n1", "r2", "n2", "r3"), merged.map(BaseItem::id))
    }

    /**
     * With no play dates to go on — the lookup failed, or Emby wrote something
     * unparseable — the row is the resume list followed by Next Up, which is the order the
     * launcher had when they were two rows. Nothing is dropped.
     */
    @Test
    fun fallsBackToResumeFirst() {
        val merged = mergeContinueWatching(
            resume = listOf(resume("r1", playedAt = "not a date"), resume("r2")),
            nextUp = listOf(upNext("n1", "alpha"), upNext("n2", "beta")),
            seriesLastPlayed = emptyMap(),
        )

        assertEquals(listOf("r1", "r2", "n1", "n2"), merged.map(BaseItem::id))
    }

    @Test
    fun normalizesEmbyTimestampsAndRejectsEverythingElse() {
        assertEquals("2026-08-06T21:04:05", normalizePlayedAt("2026-08-06T21:04:05.1234567Z"))
        assertEquals("2026-08-06T21:04:05", normalizePlayedAt("2026-08-06T21:04:05Z"))
        assertEquals("2026-08-06T21:04:05", normalizePlayedAt(" 2026-08-06T21:04:05 "))
        assertNull(normalizePlayedAt(null))
        assertNull(normalizePlayedAt(""))
        assertNull(normalizePlayedAt("2026-08-06"))
        assertNull(normalizePlayedAt("yesterday afternoon"))
        // Emby's "never played" sentinel is not a date to order a card by.
        assertNull(normalizePlayedAt("0001-01-01T00:00:00.0000000Z"))
    }
}
