package com.tencent.qian.webview.internal

import android.Manifest
import android.app.Activity
import android.app.DownloadManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import android.webkit.CookieManager
import android.webkit.DownloadListener
import android.webkit.URLUtil
import android.webkit.WebView
import androidx.core.content.ContextCompat
import com.tencent.qian.webview.R
import com.tencent.qian.webview.model.SdkEvent
import com.tencent.qian.webview.model.SignCallback
import com.tencent.qian.webview.model.TencentQianWebViewConfig
import com.tencent.qian.webview.permission.RuntimePermissionRequester

/**
 * 为 WebView 注册 [DownloadListener]。
 *
 * **策略(DownloadManager 优先 + Intent 兜底)**:
 * 1. 触发下载时发 [SdkEvent.DownloadStart] 事件
 * 2. 优先使用系统 [DownloadManager] 将文件下载到公共 Downloads 目录,
 *    通知栏显示下载进度,下载完成后用户可直接打开
 * 3. Android 6-9 (API 23-28) 需要 `WRITE_EXTERNAL_STORAGE` 权限,
 *    SDK 会自动弹出系统权限弹窗申请;用户授权后走 DownloadManager,拒绝则走兜底
 * 4. Android 10+ (API 29+):Scoped Storage 下 DownloadManager 有系统级豁免,无需存储权限
 * 5. 若 DownloadManager 不可用或 enqueue 失败,自动 fallback 到
 *    `Intent.ACTION_VIEW` 让系统(浏览器 / 文件管理)处理
 * 6. 全部失败时抛 [SdkEvent.DownloadFailed]
 *
 * **兼容性**:
 * - DownloadManager 从 Android 2.3 (API 9) 起内置,覆盖所有主流设备
 * - 用户手动禁用了系统"下载管理器"应用时 enqueue() 会抛异常,
 *   SDK 自动 fallback 到 Intent.ACTION_VIEW,对集成方透明
 *
 * **iOS 等价**:`PdfNavigationResponseHandler` + `decidePolicyFor navigationResponse`。
 */
internal object DownloadListenerInstaller {

    private const val TAG = "QianDownloadListener"

    fun install(
        webView: WebView,
        context: Context,
        permissionRequester: RuntimePermissionRequester,
        activityProvider: () -> Activity?,
        callbackProvider: () -> SignCallback?,
        config: TencentQianWebViewConfig = TencentQianWebViewConfig.DEFAULT
    ) {
        webView.setDownloadListener(DownloadListener { url, userAgent, contentDisposition, mimetype, _ ->
            // 安全校验:仅允许 http/https 协议的下载 URL,防止 intent:// 等 scheme 注入攻击
            val urlScheme = runCatching { Uri.parse(url).scheme?.lowercase() }.getOrNull()
            if (urlScheme != "http" && urlScheme != "https") {
                Log.w(TAG, "blocked download with non-http(s) scheme: $urlScheme, url=$url")
                callbackProvider()?.onEvent(
                    SdkEvent.DownloadFailed(url.orEmpty(), reason = "blocked non-http(s) scheme: $urlScheme")
                )
                return@DownloadListener
            }

            callbackProvider()?.onEvent(
                SdkEvent.DownloadStart(
                    url = url.orEmpty(),
                    mimeType = mimetype.orEmpty(),
                    contentDisposition = contentDisposition.orEmpty()
                )
            )

            // ── 优先尝试 DownloadManager ──
            // Android 6-9 (API 23-28):需要 WRITE_EXTERNAL_STORAGE 权限,未授权时动态申请
            if (Build.VERSION.SDK_INT in Build.VERSION_CODES.M until Build.VERSION_CODES.Q) {
                val hasWritePermission = ContextCompat.checkSelfPermission(
                    context, Manifest.permission.WRITE_EXTERNAL_STORAGE
                ) == PackageManager.PERMISSION_GRANTED

                if (!hasWritePermission) {
                    val activity = activityProvider()
                    if (activity == null) {
                        Log.w(TAG, "Activity unavailable, fallback to ACTION_VIEW")
                        fallbackToActionView(context, url, callbackProvider)
                        return@DownloadListener
                    }
                    Log.d(TAG, "requesting WRITE_EXTERNAL_STORAGE on API ${Build.VERSION.SDK_INT}")
                    permissionRequester.request(
                        activity,
                        arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    ) { grants ->
                        if (grants[Manifest.permission.WRITE_EXTERNAL_STORAGE] == true) {
                            Log.d(TAG, "WRITE_EXTERNAL_STORAGE granted, enqueue download")
                            if (!enqueueDownload(context, url, userAgent, contentDisposition, mimetype, config.downloadWithCookies)) {
                                fallbackToActionView(context, url, callbackProvider)
                            }
                        } else {
                            Log.d(TAG, "WRITE_EXTERNAL_STORAGE denied, fallback to ACTION_VIEW")
                            callbackProvider()?.onEvent(
                                SdkEvent.PermissionDenied(listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE))
                            )
                            fallbackToActionView(context, url, callbackProvider)
                        }
                    }
                    return@DownloadListener
                }
            }

            // 已有权限(API 23-28 已授权) 或 API 29+ 不需要权限,直接下载
            if (enqueueDownload(context, url, userAgent, contentDisposition, mimetype, config.downloadWithCookies)) {
                Log.d(TAG, "download enqueued via DownloadManager, url=$url")
                return@DownloadListener
            }

            // ── 兜底:Intent.ACTION_VIEW ──
            Log.d(TAG, "DownloadManager unavailable, fallback to ACTION_VIEW, url=$url")
            fallbackToActionView(context, url, callbackProvider)
        })
    }

    /**
     * 通过系统 DownloadManager 下载文件。
     *
     * @return `true` 表示成功入队;`false` 表示 DownloadManager 不可用或入队失败,需走兜底
     */
    private fun enqueueDownload(
        context: Context,
        url: String,
        userAgent: String?,
        contentDisposition: String?,
        mimetype: String?,
        downloadWithCookies: Boolean = false
    ): Boolean {
        return try {
            val dm = context.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager
                ?: return false

            val fileName = URLUtil.guessFileName(url, contentDisposition, mimetype)

            val request = DownloadManager.Request(Uri.parse(url)).apply {
                // 设置 MIME 类型
                if (!mimetype.isNullOrBlank()) {
                    setMimeType(mimetype)
                }
                // 携带原始 User-Agent,避免服务端拒绝
                if (!userAgent.isNullOrBlank()) {
                    addRequestHeader("User-Agent", userAgent)
                }
                // 按配置同步 Cookie 到下载请求
                if (downloadWithCookies) {
                    val cookie = CookieManager.getInstance().getCookie(url)
                    if (!cookie.isNullOrBlank()) {
                        addRequestHeader("Cookie", cookie)
                        Log.d(TAG, "injected Cookie header for download")
                    }
                }
                // 通知栏显示下载进度,下载完成后仍保留通知
                setTitle(fileName)
                setDescription(context.getString(R.string.qian_downloading))
                setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
                // 下载到公共 Downloads 目录
                // Android 10+ (Scoped Storage):DownloadManager 有系统级豁免,无需 WRITE_EXTERNAL_STORAGE
                // Android 9 及以下:调用方已确保 WRITE_EXTERNAL_STORAGE 权限已授予
                setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
            }

            dm.enqueue(request)
            true
        } catch (e: Exception) {
            // DownloadManager 被用户禁用、SecurityException、IllegalArgumentException 等
            Log.w(TAG, "DownloadManager.enqueue() failed, will fallback", e)
            false
        }
    }

    /**
     * 兜底方案:通过 Intent.ACTION_VIEW 让系统处理下载链接。
     */
    private fun fallbackToActionView(
        context: Context,
        url: String,
        callbackProvider: () -> SignCallback?
    ) {
        try {
            val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            }
            context.startActivity(intent)
        } catch (e: ActivityNotFoundException) {
            Log.w(TAG, "no Activity to handle download url=$url", e)
            callbackProvider()?.onEvent(
                SdkEvent.DownloadFailed(url.orEmpty(), reason = "ActivityNotFoundException")
            )
        } catch (e: Exception) {
            Log.w(TAG, "unexpected exception starting download url=$url", e)
            callbackProvider()?.onEvent(
                SdkEvent.DownloadFailed(url.orEmpty(), reason = e::class.java.simpleName)
            )
        }
    }
}
