package com.tencent.qian.webview.scheme

import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.tencent.qian.webview.model.SdkEvent
import com.tencent.qian.webview.model.SignCallback
import com.tencent.qian.webview.model.SignResult
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows
import org.robolectric.shadows.ShadowActivity

/**
 * [ExternalSchemeHandler] 单测。
 *
 * 覆盖场景:
 * - weixin URL 默认白名单命中 → 启动 Intent + SchemeIntercepted
 * - 空白名单 → 不启动 Intent + SchemeNotMatched
 * - 目标 App 未安装(ActivityNotFoundException)→ SchemeNotMatched
 * - SecurityException → SchemeNotMatched
 * - 未知 scheme 不在白名单 → SchemeNotMatched
 * - http(s) / qianapp 不归本 handler 处理 → 返回 false
 * - null / 空字符串 → 返回 false
 * - 大小写不敏感匹配
 * - 自定义白名单完全替换默认值
 */
@RunWith(AndroidJUnit4::class)
class ExternalSchemeHandlerTest {

    private lateinit var context: Context
    private val events = mutableListOf<SdkEvent>()
    private val callback = object : SignCallback {
        override fun onSignResult(result: SignResult) {}
        override fun onEvent(event: SdkEvent) {
            events.add(event)
        }
    }

    @Before
    fun setUp() {
        context = RuntimeEnvironment.getApplication()
        events.clear()
    }

    // ────────── 1. 白名单命中 → 启动 Intent + SchemeIntercepted ──────────

    @Test
    fun `weixin URL is forwarded when in default whitelist`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin", "alipays"),
            callbackProvider = { callback }
        )
        val url = "weixin://wxa/launchminiprogram?appid=wx123"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue("应消费此次导航", consumed)
        assertEquals(1, events.size)
        val event = events[0] as SdkEvent.SchemeIntercepted
        assertEquals("weixin", event.scheme)
        assertEquals(url, event.url)
    }

    @Test
    fun `alipays URL is forwarded when in default whitelist`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin", "alipays"),
            callbackProvider = { callback }
        )
        val url = "alipays://platformapi/startapp?appId=123"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue(consumed)
        assertEquals(1, events.size)
        val event = events[0] as SdkEvent.SchemeIntercepted
        assertEquals("alipays", event.scheme)
        assertEquals(url, event.url)
    }

    // ────────── 2. 空白名单 → SchemeNotMatched ──────────

    @Test
    fun `empty whitelist blocks all external schemes`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = emptyList(),
            callbackProvider = { callback }
        )
        val url = "weixin://wxa/launchminiprogram?appid=wx123"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue("未匹配也应消费(丢弃)", consumed)
        assertEquals(1, events.size)
        assertTrue(events[0] is SdkEvent.SchemeNotMatched)
        assertEquals(url, (events[0] as SdkEvent.SchemeNotMatched).url)
    }

    // ────────── 3. 未知 scheme 不在白名单 → SchemeNotMatched ──────────

    @Test
    fun `unknown scheme not in whitelist emits SchemeNotMatched`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin", "alipays"),
            callbackProvider = { callback }
        )
        val url = "mqqapi://chat?uin=12345"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue(consumed)
        assertEquals(1, events.size)
        assertTrue(events[0] is SdkEvent.SchemeNotMatched)
        assertEquals(url, (events[0] as SdkEvent.SchemeNotMatched).url)
    }

    // ────────── 4. http(s) 不归本 handler 处理 ──────────

    @Test
    fun `http URL returns false - not handled`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin", "alipays"),
            callbackProvider = { callback }
        )

        assertFalse(handler.tryStartActivity(context, "http://example.com"))
        assertFalse(handler.tryStartActivity(context, "https://example.com/path"))
        assertTrue(events.isEmpty())
    }

    // ────────── 5. qianapp 不归本 handler 处理(防御性兜底)──────────

    @Test
    fun `qianapp URL returns false - not handled`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin", "alipays"),
            callbackProvider = { callback }
        )

        assertFalse(handler.tryStartActivity(context, "qianapp://callback?action=sign"))
        assertTrue(events.isEmpty())
    }

    // ────────── 6. null / 空字符串 → 返回 false ──────────

    @Test
    fun `null URL returns false`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin"),
            callbackProvider = { callback }
        )

        assertFalse(handler.tryStartActivity(context, null))
        assertTrue(events.isEmpty())
    }

    @Test
    fun `empty URL returns false`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin"),
            callbackProvider = { callback }
        )

        assertFalse(handler.tryStartActivity(context, ""))
        assertTrue(events.isEmpty())
    }

    // ────────── 7. 大小写不敏感匹配 ──────────

    @Test
    fun `scheme matching is case insensitive`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin", "alipays"),
            callbackProvider = { callback }
        )
        val url = "WEIXIN://wxa/launchminiprogram?appid=wx123"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue(consumed)
        assertEquals(1, events.size)
        val event = events[0] as SdkEvent.SchemeIntercepted
        assertEquals("weixin", event.scheme)
    }

    @Test
    fun `whitelist entry case insensitive`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("WEIXIN", "ALIPAYS"),
            callbackProvider = { callback }
        )
        val url = "weixin://wxa/test"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue(consumed)
        assertEquals(1, events.size)
        assertTrue(events[0] is SdkEvent.SchemeIntercepted)
    }

    // ────────── 8. 自定义白名单完全替换默认值 ──────────

    @Test
    fun `custom whitelist replaces default - weixin blocked when not in custom list`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("mqqapi"),
            callbackProvider = { callback }
        )
        val url = "weixin://wxa/test"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue("未匹配也应消费(丢弃)", consumed)
        assertEquals(1, events.size)
        assertTrue(events[0] is SdkEvent.SchemeNotMatched)
    }

    @Test
    fun `custom whitelist allows custom scheme`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("mqqapi", "tbopen"),
            callbackProvider = { callback }
        )
        val url = "mqqapi://chat?uin=12345"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue(consumed)
        assertEquals(1, events.size)
        assertTrue(events[0] is SdkEvent.SchemeIntercepted)
    }

    // ────────── 9. callback 为 null 时不崩溃 ──────────

    @Test
    fun `null callback does not crash on matched scheme`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin"),
            callbackProvider = { null }
        )
        val url = "weixin://wxa/test"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue(consumed)
        // 无 callback,不应崩溃
    }

    @Test
    fun `null callback does not crash on unmatched scheme`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = emptyList(),
            callbackProvider = { null }
        )
        val url = "weixin://wxa/test"

        val consumed = handler.tryStartActivity(context, url)

        assertTrue(consumed)
        // 无 callback,不应崩溃
    }

    // ────────── 10. 无 scheme 的 URL → 返回 false ──────────

    @Test
    fun `URL without scheme returns false`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin"),
            callbackProvider = { callback }
        )

        // 无 scheme 的字符串,Uri.parse 后 scheme 为 null
        assertFalse(handler.tryStartActivity(context, "just-a-path"))
        assertTrue(events.isEmpty())
    }

    // ────────── 11. 畸形 URL 不崩溃 ──────────

    @Test
    fun `malformed URL does not crash`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin"),
            callbackProvider = { callback }
        )

        // "://" → Uri.parse 可能解析出空 scheme,不匹配白名单 → SchemeNotMatched + 返回 true
        // 关键是不崩溃
        handler.tryStartActivity(context, "://")
        // 纯空格 → isNullOrEmpty 为 false,但 Uri.parse 后 scheme 可能为 null → 返回 false
        // 或者 scheme 为空字符串 → 不匹配 → SchemeNotMatched
        handler.tryStartActivity(context, "   ")
        // 无异常即通过
    }

    // ────────── 12. Intent.FLAG_ACTIVITY_NEW_TASK 被设置 ──────────

    @Test
    fun `started intent has FLAG_ACTIVITY_NEW_TASK`() {
        val handler = ExternalSchemeHandler(
            externalSchemes = listOf("weixin"),
            callbackProvider = { callback }
        )
        val url = "weixin://wxa/test"

        handler.tryStartActivity(context, url)

        // Robolectric 的 ShadowApplication 记录了 startActivity 调用
        val shadowApp = Shadows.shadowOf(RuntimeEnvironment.getApplication())
        val nextIntent = shadowApp.nextStartedActivity
        if (nextIntent != null) {
            assertTrue(
                "Intent 应包含 FLAG_ACTIVITY_NEW_TASK",
                nextIntent.flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0
            )
            assertEquals(Intent.ACTION_VIEW, nextIntent.action)
            assertEquals(url, nextIntent.data.toString())
        }
    }
}
