package com.swoosh.microblog.ui.feed

import com.swoosh.microblog.data.model.FeedPost
import com.swoosh.microblog.data.model.QueueStatus
import org.junit.Assert.*
import org.junit.Test

/**
 * Pure unit tests for search deduplication logic and text highlighting.
 * No Android framework dependencies needed.
 */
class SearchDeduplicationTest {

    @Test
    fun `deduplicateResults returns all posts when no overlap`() {
        val local = listOf(
            createFeedPost(localId = 1, ghostId = null, title = "Local Draft")
        )
        val remote = listOf(
            createFeedPost(ghostId = "ghost-1", title = "Remote Post")
        )

        val result = FeedViewModel.deduplicateResults(local, remote)

        assertEquals(2, result.size)
    }

    @Test
    fun `deduplicateResults prefers remote when ghostId matches`() {
        val local = listOf(
            createFeedPost(localId = 1, ghostId = "ghost-1", title = "Local Version", textContent = "old content")
        )
        val remote = listOf(
            createFeedPost(ghostId = "ghost-1", title = "Remote Version", textContent = "new content")
        )

        val result = FeedViewModel.deduplicateResults(local, remote)

        assertEquals(1, result.size)
        assertEquals("Remote Version", result[0].title)
        assertEquals("new content", result[0].textContent)
    }

    @Test
    fun `deduplicateResults keeps local posts without ghostId`() {
        val local = listOf(
            createFeedPost(localId = 1, ghostId = null, title = "Draft 1"),
            createFeedPost(localId = 2, ghostId = null, title = "Draft 2")
        )
        val remote = listOf(
            createFeedPost(ghostId = "ghost-1", title = "Published Post")
        )

        val result = FeedViewModel.deduplicateResults(local, remote)

        assertEquals(3, result.size)
    }

    @Test
    fun `deduplicateResults with empty local returns only remote`() {
        val local = emptyList<FeedPost>()
        val remote = listOf(
            createFeedPost(ghostId = "ghost-1", title = "Remote 1"),
            createFeedPost(ghostId = "ghost-2", title = "Remote 2")
        )

        val result = FeedViewModel.deduplicateResults(local, remote)

        assertEquals(2, result.size)
    }

    @Test
    fun `deduplicateResults with empty remote returns only local`() {
        val local = listOf(
            createFeedPost(localId = 1, ghostId = null, title = "Draft 1")
        )
        val remote = emptyList<FeedPost>()

        val result = FeedViewModel.deduplicateResults(local, remote)

        assertEquals(1, result.size)
        assertEquals("Draft 1", result[0].title)
    }

    @Test
    fun `deduplicateResults with both empty returns empty list`() {
        val result = FeedViewModel.deduplicateResults(emptyList(), emptyList())
        assertTrue(result.isEmpty())
    }

    @Test
    fun `deduplicateResults removes multiple local duplicates of same ghostId`() {
        val local = listOf(
            createFeedPost(localId = 1, ghostId = "ghost-1", title = "Local V1"),
            createFeedPost(localId = 2, ghostId = "ghost-1", title = "Local V2")
        )
        val remote = listOf(
            createFeedPost(ghostId = "ghost-1", title = "Remote Version")
        )

        val result = FeedViewModel.deduplicateResults(local, remote)

        assertEquals(1, result.size)
        assertEquals("Remote Version", result[0].title)
    }

    @Test
    fun `deduplicateResults handles mixed local with and without ghostIds`() {
        val local = listOf(
            createFeedPost(localId = 1, ghostId = null, title = "Draft"),
            createFeedPost(localId = 2, ghostId = "ghost-1", title = "Local Synced")
        )
        val remote = listOf(
            createFeedPost(ghostId = "ghost-1", title = "Remote Synced"),
            createFeedPost(ghostId = "ghost-2", title = "Remote Only")
        )

        val result = FeedViewModel.deduplicateResults(local, remote)

        assertEquals(3, result.size)
        val titles = result.map { it.title }
        assertTrue(titles.contains("Draft"))
        assertTrue(titles.contains("Remote Synced"))
        assertTrue(titles.contains("Remote Only"))
        assertFalse(titles.contains("Local Synced"))
    }

    private fun createFeedPost(
        localId: Long? = null,
        ghostId: String? = null,
        title: String = "",
        textContent: String = "",
        status: String = "draft",
        publishedAt: String? = null,
        createdAt: String? = null
    ) = FeedPost(
        localId = localId,
        ghostId = ghostId,
        title = title,
        textContent = textContent,
        htmlContent = null,
        imageUrl = null,
        linkUrl = null,
        linkTitle = null,
        linkDescription = null,
        linkImageUrl = null,
        status = status,
        publishedAt = publishedAt,
        createdAt = createdAt,
        updatedAt = null,
        isLocal = localId != null,
        queueStatus = QueueStatus.NONE
    )
}

/**
 * Tests for text highlighting logic used in search results.
 * Pure unit tests, no Android dependencies.
 */
class SearchHighlightTest {

    @Test
    fun `buildHighlightedString with no match returns plain text`() {
        val result = buildHighlightedString("Hello World", "xyz")

        assertEquals("Hello World", result.text)
        assertEquals(0, result.spanStyles.size)
    }

    @Test
    fun `buildHighlightedString highlights single match`() {
        val result = buildHighlightedString("Hello World", "World")

        assertEquals("Hello World", result.text)
        assertEquals(1, result.spanStyles.size)
        assertEquals(6, result.spanStyles[0].start)
        assertEquals(11, result.spanStyles[0].end)
    }

    @Test
    fun `buildHighlightedString highlights multiple matches`() {
        val result = buildHighlightedString("cat and cat and cat", "cat")

        assertEquals("cat and cat and cat", result.text)
        assertEquals(3, result.spanStyles.size)
    }

    @Test
    fun `buildHighlightedString is case insensitive`() {
        val result = buildHighlightedString("Hello HELLO hello", "hello")

        assertEquals("Hello HELLO hello", result.text)
        assertEquals(3, result.spanStyles.size)
    }

    @Test
    fun `buildHighlightedString with empty query returns plain text`() {
        val result = buildHighlightedString("Hello World", "")

        assertEquals("Hello World", result.text)
        assertEquals(0, result.spanStyles.size)
    }

    @Test
    fun `buildHighlightedString with blank query returns plain text`() {
        val result = buildHighlightedString("Hello World", "   ")

        assertEquals("Hello World", result.text)
        assertEquals(0, result.spanStyles.size)
    }

    @Test
    fun `buildHighlightedString with query at start of text`() {
        val result = buildHighlightedString("Hello World", "Hello")

        assertEquals("Hello World", result.text)
        assertEquals(1, result.spanStyles.size)
        assertEquals(0, result.spanStyles[0].start)
        assertEquals(5, result.spanStyles[0].end)
    }

    @Test
    fun `buildHighlightedString with query at end of text`() {
        val result = buildHighlightedString("Hello World", "World")

        assertEquals("Hello World", result.text)
        assertEquals(1, result.spanStyles.size)
        assertEquals(6, result.spanStyles[0].start)
        assertEquals(11, result.spanStyles[0].end)
    }

    @Test
    fun `buildHighlightedString with entire text matching`() {
        val result = buildHighlightedString("hello", "hello")

        assertEquals("hello", result.text)
        assertEquals(1, result.spanStyles.size)
        assertEquals(0, result.spanStyles[0].start)
        assertEquals(5, result.spanStyles[0].end)
    }

    @Test
    fun `buildHighlightedString preserves original case`() {
        val result = buildHighlightedString("Hello WORLD", "hello")

        assertEquals("Hello WORLD", result.text)
    }

    @Test
    fun `buildHighlightedString with overlapping potential matches`() {
        val result = buildHighlightedString("aaa", "aa")

        assertEquals("aaa", result.text)
        // Should find first match at 0-2, then next search starts at 2
        assertEquals(1, result.spanStyles.size)
        assertEquals(0, result.spanStyles[0].start)
        assertEquals(2, result.spanStyles[0].end)
    }

    @Test
    fun `buildHighlightedString with single character query`() {
        val result = buildHighlightedString("banana", "a")

        assertEquals("banana", result.text)
        assertEquals(3, result.spanStyles.size)
    }
}

/**
 * Tests for SearchHistoryManager.
 * Uses Robolectric for SharedPreferences access.
 */
@org.junit.runner.RunWith(org.robolectric.RobolectricTestRunner::class)
@org.robolectric.annotation.Config(
    sdk = [28],
    manifest = org.robolectric.annotation.Config.NONE,
    application = android.app.Application::class
)
class SearchHistoryManagerTest {

    private lateinit var searchHistoryManager: SearchHistoryManager

    @org.junit.Before
    fun setup() {
        val context = org.robolectric.RuntimeEnvironment.getApplication()
        context.getSharedPreferences(SearchHistoryManager.PREFS_NAME, android.content.Context.MODE_PRIVATE)
            .edit().clear().apply()
        searchHistoryManager = SearchHistoryManager(context)
    }

    @Test
    fun `getRecentSearches returns empty list initially`() {
        val result = searchHistoryManager.getRecentSearches()
        assertTrue(result.isEmpty())
    }

    @Test
    fun `addSearch adds query to history`() {
        searchHistoryManager.addSearch("test query")

        val result = searchHistoryManager.getRecentSearches()
        assertEquals(1, result.size)
        assertEquals("test query", result[0])
    }

    @Test
    fun `addSearch adds newest first`() {
        searchHistoryManager.addSearch("first")
        searchHistoryManager.addSearch("second")

        val result = searchHistoryManager.getRecentSearches()
        assertEquals("second", result[0])
        assertEquals("first", result[1])
    }

    @Test
    fun `addSearch limits to MAX_HISTORY items`() {
        repeat(7) { i ->
            searchHistoryManager.addSearch("query $i")
        }

        val result = searchHistoryManager.getRecentSearches()
        assertEquals(SearchHistoryManager.MAX_HISTORY, result.size)
    }

    @Test
    fun `addSearch deduplicates same query`() {
        searchHistoryManager.addSearch("test")
        searchHistoryManager.addSearch("other")
        searchHistoryManager.addSearch("test") // Duplicate

        val result = searchHistoryManager.getRecentSearches()
        assertEquals(2, result.size)
        assertEquals("test", result[0]) // Most recent
        assertEquals("other", result[1])
    }

    @Test
    fun `addSearch trims whitespace`() {
        searchHistoryManager.addSearch("  test  ")

        val result = searchHistoryManager.getRecentSearches()
        assertEquals(1, result.size)
        assertEquals("test", result[0])
    }

    @Test
    fun `addSearch ignores blank queries`() {
        searchHistoryManager.addSearch("")
        searchHistoryManager.addSearch("   ")

        val result = searchHistoryManager.getRecentSearches()
        assertTrue(result.isEmpty())
    }

    @Test
    fun `removeSearch removes specific query`() {
        searchHistoryManager.addSearch("first")
        searchHistoryManager.addSearch("second")
        searchHistoryManager.addSearch("third")

        searchHistoryManager.removeSearch("second")

        val result = searchHistoryManager.getRecentSearches()
        assertEquals(2, result.size)
        assertFalse(result.contains("second"))
    }

    @Test
    fun `removeSearch does nothing for nonexistent query`() {
        searchHistoryManager.addSearch("first")

        searchHistoryManager.removeSearch("nonexistent")

        val result = searchHistoryManager.getRecentSearches()
        assertEquals(1, result.size)
    }

    @Test
    fun `clearAll removes all searches`() {
        searchHistoryManager.addSearch("first")
        searchHistoryManager.addSearch("second")

        searchHistoryManager.clearAll()

        val result = searchHistoryManager.getRecentSearches()
        assertTrue(result.isEmpty())
    }

    @Test
    fun `search history persists across instances`() {
        searchHistoryManager.addSearch("persistent query")

        val context = org.robolectric.RuntimeEnvironment.getApplication()
        val newManager = SearchHistoryManager(context)
        val result = newManager.getRecentSearches()

        assertEquals(1, result.size)
        assertEquals("persistent query", result[0])
    }

    @Test
    fun `addSearch moves existing query to front`() {
        searchHistoryManager.addSearch("first")
        searchHistoryManager.addSearch("second")
        searchHistoryManager.addSearch("third")
        searchHistoryManager.addSearch("first") // Re-add

        val result = searchHistoryManager.getRecentSearches()
        assertEquals(3, result.size)
        assertEquals("first", result[0])
        assertEquals("third", result[1])
        assertEquals("second", result[2])
    }
}
