package com.tencent.qian.webview.internal;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import android.Manifest;
import android.app.DownloadManager;
import android.content.Context;
import android.os.Build;
import android.webkit.DownloadListener;
import android.webkit.WebView;

import androidx.activity.ComponentActivity;
import androidx.core.util.Supplier;
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 com.tencent.qian.webview.model.TencentQianWebViewConfig;
import com.tencent.qian.webview.permission.RuntimePermissionRequester;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.Shadows;
import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config;

import java.util.ArrayList;
import java.util.List;

/**
 * {@link DownloadListenerInstaller} 单测。
 *
 * <p>覆盖场景:
 * <ul>
 *   <li>非 http(s) scheme 被阻断 + DownloadFailed 事件</li>
 *   <li>DownloadStart 事件正确发出(url / mimeType / contentDisposition)</li>
 *   <li>Android 10+ 不请求 WRITE_EXTERNAL_STORAGE,直接走 DownloadManager</li>
 *   <li>Android 6-9 未授权时请求权限;授权后走 DownloadManager;拒绝后 fallback</li>
 *   <li>DownloadManager 不可用时 fallback 到 ACTION_VIEW</li>
 *   <li>ACTION_VIEW 也失败时发 DownloadFailed</li>
 *   <li>null callback 不崩溃</li>
 *   <li>多次下载触发多次事件</li>
 * </ul>
 *
 * <p>注:使用 Robolectric shadow 来模拟 DownloadManager 和权限状态。
 */
@RunWith(AndroidJUnit4.class)
public class DownloadListenerInstallerTest {

    private Context context;
    private ActivityController<ComponentActivity> activityController;
    private ComponentActivity activity;
    private RuntimePermissionRequester permissionRequester;
    private final List<SdkEvent> events = new ArrayList<>();
    private final SignCallback callback = new SignCallback() {
        @Override
        public void onSignResult(SignResult result) {
        }

        @Override
        public void onEvent(SdkEvent event) {
            events.add(event);
        }
    };

    @Before
    public void setUp() {
        context = RuntimeEnvironment.getApplication();
        activityController = Robolectric.buildActivity(ComponentActivity.class);
        // 注册 permissionRequester 必须在 onCreate 之前
        activity = activityController.get();
        permissionRequester = RuntimePermissionRequester.register(activity);
        activityController.create();
        events.clear();
    }

    private DownloadListener installAndGetListener() {
        WebView webView = new WebView(context);
        DownloadListenerInstaller.install(
                webView,
                context,
                permissionRequester,
                () -> activity,
                () -> callback,
                TencentQianWebViewConfig.DEFAULT
        );
        return Shadows.shadowOf(webView).getDownloadListener();
    }

    // ────────── 1. 非 http(s) scheme 被阻断 ──────────

    @Test
    public void blocks_intent_scheme_and_emits_DownloadFailed() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "intent://scan/#Intent;scheme=zxing;end",
                "Mozilla/5.0",
                "attachment; filename=test.apk",
                "application/vnd.android.package-archive",
                1024
        );

        assertEquals(1, events.size());
        SdkEvent.DownloadFailed event = (SdkEvent.DownloadFailed) events.get(0);
        assertTrue(event.getReason().contains("non-http(s) scheme"));
        assertEquals("intent://scan/#Intent;scheme=zxing;end", event.getUrl());
    }

    @Test
    public void blocks_javascript_scheme_and_emits_DownloadFailed() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "javascript:alert(1)",
                "Mozilla/5.0",
                "",
                "text/html",
                0
        );

        assertEquals(1, events.size());
        assertTrue(events.get(0) instanceof SdkEvent.DownloadFailed);
    }

    @Test
    public void blocks_data_scheme_and_emits_DownloadFailed() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "data:text/html,<h1>test</h1>",
                "Mozilla/5.0",
                "",
                "text/html",
                100
        );

        assertEquals(1, events.size());
        assertTrue(events.get(0) instanceof SdkEvent.DownloadFailed);
    }

    @Test
    public void blocks_ftp_scheme_and_emits_DownloadFailed() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "ftp://files.example.com/secret.zip",
                "Mozilla/5.0",
                "",
                "application/zip",
                4096
        );

        assertEquals(1, events.size());
        assertTrue(events.get(0) instanceof SdkEvent.DownloadFailed);
    }

    // ────────── 2. http(s) URL 发出 DownloadStart 事件 ──────────

    @Test
    @Config(sdk = Build.VERSION_CODES.Q) // API 29
    public void https_URL_emits_DownloadStart_event_with_correct_fields() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "https://example.com/contract.pdf",
                "Mozilla/5.0",
                "attachment; filename=contract.pdf",
                "application/pdf",
                2048
        );

        assertTrue(!events.isEmpty());
        SdkEvent.DownloadStart startEvent = (SdkEvent.DownloadStart) events.get(0);
        assertEquals("https://example.com/contract.pdf", startEvent.getUrl());
        assertEquals("application/pdf", startEvent.getMimeType());
        assertEquals("attachment; filename=contract.pdf", startEvent.getContentDisposition());
    }

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void http_URL_also_emits_DownloadStart() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "http://example.com/file.zip",
                "Mozilla/5.0",
                "attachment; filename=file.zip",
                "application/zip",
                4096
        );

        assertTrue(!events.isEmpty());
        SdkEvent.DownloadStart startEvent = (SdkEvent.DownloadStart) events.get(0);
        assertEquals("http://example.com/file.zip", startEvent.getUrl());
        assertEquals("application/zip", startEvent.getMimeType());
    }

    // ────────── 3. Android 10+ 直接走 DownloadManager ──────────

    @Test
    @Config(sdk = Build.VERSION_CODES.Q) // API 29
    public void API_29_enqueues_via_DownloadManager_without_permission_request() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "https://example.com/contract.pdf",
                "Mozilla/5.0",
                "attachment; filename=contract.pdf",
                "application/pdf",
                2048
        );

        // 验证 DownloadManager 被调用
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        assertTrue("应有下载请求入队", Shadows.shadowOf(dm).getRequestCount() > 0);
        // 不应有 PermissionDenied 事件
        assertTrue(events.stream().noneMatch(e -> e instanceof SdkEvent.PermissionDenied));
    }

    @Test
    @Config(sdk = Build.VERSION_CODES.TIRAMISU) // API 33
    public void API_33_enqueues_via_DownloadManager_without_permission_request() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "https://example.com/report.xlsx",
                "Mozilla/5.0",
                "attachment; filename=report.xlsx",
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                65536
        );

        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        assertTrue(Shadows.shadowOf(dm).getRequestCount() > 0);
    }

    // ────────── 4. Android 6-9 权限已授予时直接下载 ──────────

    @Test
    @Config(sdk = Build.VERSION_CODES.O) // API 26
    public void API_26_with_granted_permission_enqueues_directly() {
        // 通过 Robolectric shadow 授予权限
        Shadows.shadowOf(RuntimeEnvironment.getApplication())
                .grantPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE);

        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "https://example.com/contract.pdf",
                "Mozilla/5.0",
                "attachment; filename=contract.pdf",
                "application/pdf",
                2048
        );

        // 应该有 DownloadStart 事件
        assertTrue(events.stream().anyMatch(e -> e instanceof SdkEvent.DownloadStart));
        // 不应有 PermissionDenied
        assertTrue(events.stream().noneMatch(e -> e instanceof SdkEvent.PermissionDenied));
    }

    // ────────── 5. null callback 不崩溃 ──────────

    @Test
    public void null_callback_does_not_crash_on_blocked_scheme() {
        WebView webView = new WebView(context);
        DownloadListenerInstaller.install(
                webView,
                context,
                permissionRequester,
                () -> activity,
                () -> null,
                TencentQianWebViewConfig.DEFAULT
        );
        DownloadListener listener = Shadows.shadowOf(webView).getDownloadListener();

        // 不应崩溃
        listener.onDownloadStart(
                "intent://bad",
                "Mozilla/5.0",
                "",
                "application/octet-stream",
                0
        );
    }

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void null_callback_does_not_crash_on_valid_download() {
        WebView webView = new WebView(context);
        DownloadListenerInstaller.install(
                webView,
                context,
                permissionRequester,
                () -> activity,
                () -> null,
                TencentQianWebViewConfig.DEFAULT
        );
        DownloadListener listener = Shadows.shadowOf(webView).getDownloadListener();

        listener.onDownloadStart(
                "https://example.com/file.pdf",
                "Mozilla/5.0",
                "attachment; filename=file.pdf",
                "application/pdf",
                1024
        );
        // 无异常即通过
    }

    // ────────── 6. 多次下载触发多次事件 ──────────

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void multiple_downloads_emit_multiple_DownloadStart_events() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "https://example.com/file1.pdf",
                "Mozilla/5.0", "", "application/pdf", 1024
        );
        listener.onDownloadStart(
                "https://example.com/file2.zip",
                "Mozilla/5.0", "", "application/zip", 2048
        );

        long startCount = events.stream().filter(e -> e instanceof SdkEvent.DownloadStart).count();
        assertEquals(2, startCount);
    }

    // ────────── 7. 空 URL 处理 ──────────

    @Test
    public void empty_url_emits_DownloadFailed() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "",
                "Mozilla/5.0",
                "",
                "application/pdf",
                0
        );

        assertTrue(events.stream().anyMatch(e -> e instanceof SdkEvent.DownloadFailed));
    }

    // ────────── 8. Activity 不可用时 fallback ──────────

    @Test
    @Config(sdk = Build.VERSION_CODES.O) // API 26, 需要权限
    public void null_activity_provider_falls_back_gracefully() {
        WebView webView = new WebView(context);
        DownloadListenerInstaller.install(
                webView,
                context,
                permissionRequester,
                () -> null,
                () -> callback,
                TencentQianWebViewConfig.DEFAULT
        );
        DownloadListener listener = Shadows.shadowOf(webView).getDownloadListener();

        listener.onDownloadStart(
                "https://example.com/contract.pdf",
                "Mozilla/5.0",
                "attachment; filename=contract.pdf",
                "application/pdf",
                2048
        );

        // 应该有 DownloadStart 事件
        assertTrue(events.stream().anyMatch(e -> e instanceof SdkEvent.DownloadStart));
        // Activity 不可用,无法弹权限,应 fallback(不崩溃)
    }

    // ────────── 9. MIME 和 contentDisposition 为 null/空时不崩溃 ──────────

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void null_mimetype_and_contentDisposition_handled_gracefully() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "https://example.com/unknown",
                "Mozilla/5.0",
                null,
                null,
                512
        );

        // 应该有 DownloadStart 事件,字段为空字符串
        SdkEvent.DownloadStart startEvent = null;
        for (SdkEvent e : events) {
            if (e instanceof SdkEvent.DownloadStart) {
                startEvent = (SdkEvent.DownloadStart) e;
                break;
            }
        }
        assertTrue("应有 DownloadStart 事件", startEvent != null);
        assertEquals("", startEvent.getMimeType());
        assertEquals("", startEvent.getContentDisposition());
    }

    // ────────── 10. scheme 大小写不敏感 ──────────

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void HTTPS_uppercase_scheme_is_allowed() {
        DownloadListener listener = installAndGetListener();

        listener.onDownloadStart(
                "HTTPS://example.com/file.pdf",
                "Mozilla/5.0",
                "attachment; filename=file.pdf",
                "application/pdf",
                1024
        );

        // 应该有 DownloadStart(不应被阻断)
        assertTrue(events.stream().anyMatch(e -> e instanceof SdkEvent.DownloadStart));
        assertTrue(events.stream().noneMatch(e -> e instanceof SdkEvent.DownloadFailed));
    }
}
