package com.aryan.reader.epubreader

import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Rule
import org.junit.Test

@OptIn(ExperimentalCoroutinesApi::class)
class ChapterWebViewBridgeTest {

    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    @Test
    fun cfiJsBridge_onCfiExtracted_callsCallbackWithCorrectCfi() {
        var receivedCfi = ""
        val bridge = CfiJsBridge(
            onCfiReady = { cfi -> receivedCfi = cfi },
            onCfiForBookmarkReady = {}
        )

        val cfi = "/4/2[chapter1]/6:10"
        val jsonResponse = JSONObject().apply {
            put("cfi", cfi)
            put("log", JSONArray(listOf("log message 1", "log message 2")))
        }.toString()

        bridge.onCfiExtracted(jsonResponse)

        assertThat(receivedCfi).isEqualTo(cfi)
    }

    @Test
    fun cfiJsBridge_onCfiExtracted_withInvalidJson_callsCallbackWithFallbackCfi() {
        var receivedCfi = ""
        val bridge = CfiJsBridge(
            onCfiReady = { cfi -> receivedCfi = cfi },
            onCfiForBookmarkReady = {}
        )

        val invalidJson = "this is not json"

        bridge.onCfiExtracted(invalidJson)

        assertThat(receivedCfi).isEqualTo("/4")
    }

    @Test
    fun cfiJsBridge_onCfiExtracted_withEmptyCfi_callsCallbackWithCfi() {
        var receivedCfi: String? = null
        val bridge = CfiJsBridge(
            onCfiReady = { cfi -> receivedCfi = cfi },
            onCfiForBookmarkReady = {}
        )

        val jsonResponse = JSONObject().apply {
            put("cfi", "")
            put("log", JSONArray())
        }.toString()

        bridge.onCfiExtracted(jsonResponse)

        // The handler is only called if the CFI is not blank, so it should remain null
        assertThat(receivedCfi).isNull()
    }

    @Test
    fun ttsJsBridge_onStructuredTextExtracted_callsHandlerWithJson() = runTest {
        var receivedJson: String? = null
        val bridge = TtsJsBridge(
            scope = this,
            ttsStructuredTextHandler = { json -> receivedJson = json }
        )
        val jsonPayload = "[{\"text\":\"Hello world\",\"cfi\":\"/4/2\"}]"
        bridge.onStructuredTextExtracted(jsonPayload)
        advanceUntilIdle()
        assertThat(receivedJson).isEqualTo(jsonPayload)
    }

    @Test
    fun ttsJsBridge_onStructuredTextExtracted_withEmptyJson_callsHandlerWithEmptyArray() = runTest {
        var receivedJson: String? = null
        val bridge = TtsJsBridge(
            scope = this,
            ttsStructuredTextHandler = { json -> receivedJson = json }
        )
        val jsonPayload = ""
        bridge.onStructuredTextExtracted(jsonPayload)
        advanceUntilIdle()
        assertThat(receivedJson).isEqualTo("[]")
    }

    @Test
    fun snippetJsBridge_onSnippetExtracted_callsCallbackWithCfiAndSnippet() {
        var receivedCfi = ""
        var receivedSnippet = ""
        val bridge = SnippetJsBridge(
            onSnippetReady = { cfi, snippet ->
                receivedCfi = cfi
                receivedSnippet = snippet
            }
        )

        val cfi = "/4/8:5"
        val snippet = "This is the bookmark snippet."
        bridge.onSnippetExtracted(cfi, snippet)

        assertThat(receivedCfi).isEqualTo(cfi)
        assertThat(receivedSnippet).isEqualTo(snippet)
    }

    @Test
    fun progressJsBridge_onTopChunkUpdated_invokesCallback() {
        var updatedChunk = -1
        val bridge = ProgressJsBridge(onTopChunkUpdated = { index -> updatedChunk = index })

        bridge.updateTopChunk(5)
        assertThat(updatedChunk).isEqualTo(5)

        bridge.updateTopChunk(10)
        assertThat(updatedChunk).isEqualTo(10)
    }

    @Test
    fun progressJsBridge_onTopChunkUpdated_doesNotCallForSameIndex() {
        var callCount = 0
        val bridge = ProgressJsBridge(onTopChunkUpdated = { callCount++ })

        bridge.updateTopChunk(3)
        assertThat(callCount).isEqualTo(1)

        // Reporting the same index should not trigger the callback again
        bridge.updateTopChunk(3)
        assertThat(callCount).isEqualTo(1)

        bridge.updateTopChunk(4)
        assertThat(callCount).isEqualTo(2)
    }

    @Test
    fun aiJsBridge_onContentExtracted_invokesCallback() = runTest {
        var receivedContent: String? = null
        val bridge = AiJsBridge(
            scope = this,
            onContentReady = { content -> receivedContent = content }
        )
        val content = "This is the chapter content for summarization."
        bridge.onContentExtractedForSummarization(content)
        advanceUntilIdle()
        assertThat(receivedContent).isEqualTo(content)
    }
}