import XCTest
@testable import TencentQianWebViewSDK

/// `QianNavigationDelegate` 导航决策(URL 路由优先级)单测。
///
/// 与 Android `QianWebViewClientOverrideUrlTest` 对齐,覆盖 `decidePolicyFor navigationAction` 的 4 级优先级:
/// 1. qianapp:// → SchemeInterceptor 拦截 → .cancel
/// 2. http(s):// → 放行 → .allow
/// 3. externalSchemes 白名单命中 → .cancel
/// 4. 未知 scheme → .cancel + schemeNotMatched
/// + nil URL → .cancel
///
/// **测试策略**:
/// 由于 `WKNavigationAction` 无法直接构造(Apple 未开放 init),
/// 本测试直接测试 `SchemeInterceptor` + `ExternalSchemeHandler` 的组合路由逻辑,
/// 模拟 `QianNavigationDelegate` 内部 `decidePolicyFor` 的决策流程。
final class QianNavigationDelegateTests: XCTestCase {

    private var events: [SdkEvent] = []
    private var signResults: [SignResult] = []

    override func setUp() {
        super.setUp()
        events = []
        signResults = []
    }

    /// 模拟 QianNavigationDelegate.decidePolicyFor 的核心路由逻辑
    /// (提取自 QianNavigationDelegate,与其实现 1:1 对齐)
    private func simulateDecision(
        urlString: String?,
        externalSchemes: [String] = ["weixin", "alipays"]
    ) -> NavigationDecision {
        guard let urlString = urlString, !urlString.isEmpty,
              let url = URL(string: urlString) else {
            return .cancel
        }

        // 1. qianapp
        let schemeInterceptor = SchemeInterceptor(
            onSignResult: { [weak self] result in self?.signResults.append(result) },
            onEvent: { [weak self] event in self?.events.append(event) }
        )
        if schemeInterceptor.handle(urlString) {
            return .cancel
        }

        // 2. http(s) 走默认
        let scheme = url.scheme?.lowercased() ?? ""
        if scheme == "http" || scheme == "https" {
            return .allow
        }

        // 3 & 4. external + 兜底
        let externalHandler = ExternalSchemeHandler(
            externalSchemes: externalSchemes,
            onEvent: { [weak self] event in self?.events.append(event) }
        )
        if externalHandler.tryOpen(urlString) {
            return .cancel
        }

        return .cancel
    }

    /// 决策结果枚举(模拟 WKNavigationActionPolicy)
    enum NavigationDecision: Equatable {
        case allow
        case cancel
    }

    // MARK: - 优先级 1: qianapp:// → 拦截

    func test_qianapp_url_is_intercepted() {
        let decision = simulateDecision(urlString: "qianapp://callback/sign?action=sign&result=success&flowId=abc")

        XCTAssertEqual(decision, .cancel)
        // SchemeInterceptor 会异步派发 onSignResult,但 handle() 同步返回 true
    }

    func test_qianapp_uppercase_is_intercepted() {
        let decision = simulateDecision(urlString: "QIANAPP://x?action=fill")

        XCTAssertEqual(decision, .cancel)
    }

    // MARK: - 优先级 2: http(s) → 放行

    func test_http_url_is_allowed() {
        let decision = simulateDecision(urlString: "http://example.com/page")

        XCTAssertEqual(decision, .allow)
        XCTAssertTrue(events.isEmpty)
    }

    func test_https_url_is_allowed() {
        let decision = simulateDecision(urlString: "https://quick.qian.tencent.cn/sign")

        XCTAssertEqual(decision, .allow)
        XCTAssertTrue(events.isEmpty)
    }

    func test_https_uppercase_is_allowed() {
        let decision = simulateDecision(urlString: "HTTPS://EXAMPLE.COM/PATH")

        XCTAssertEqual(decision, .allow)
    }

    // MARK: - 优先级 3: externalSchemes 白名单 → cancel

    func test_weixin_in_whitelist_is_cancelled() {
        let decision = simulateDecision(
            urlString: "weixin://wxa/launchminiprogram?appid=wx123",
            externalSchemes: ["weixin", "alipays"]
        )

        XCTAssertEqual(decision, .cancel)
    }

    func test_alipays_in_whitelist_is_cancelled() {
        let decision = simulateDecision(
            urlString: "alipays://platformapi/startapp?appId=123",
            externalSchemes: ["weixin", "alipays"]
        )

        XCTAssertEqual(decision, .cancel)
    }

    // MARK: - 优先级 4: 未知 scheme → cancel + schemeNotMatched

    func test_unknown_scheme_emits_schemeNotMatched() {
        let decision = simulateDecision(
            urlString: "mqqapi://chat?uin=12345",
            externalSchemes: ["weixin", "alipays"]
        )

        XCTAssertEqual(decision, .cancel)
        XCTAssertEqual(events.count, 1)
        if case .schemeNotMatched(let url) = events.first {
            XCTAssertEqual(url, "mqqapi://chat?uin=12345")
        } else {
            XCTFail("应发出 schemeNotMatched 事件")
        }
    }

    // MARK: - 边界: nil / 空 → cancel

    func test_nil_url_returns_cancel() {
        let decision = simulateDecision(urlString: nil)

        XCTAssertEqual(decision, .cancel)
        XCTAssertTrue(events.isEmpty)
    }

    func test_empty_url_returns_cancel() {
        let decision = simulateDecision(urlString: "")

        XCTAssertEqual(decision, .cancel)
        XCTAssertTrue(events.isEmpty)
    }

    // MARK: - 优先级验证: qianapp 优先于 externalSchemes

    func test_qianapp_takes_priority_over_externalSchemes() {
        // 即使 externalSchemes 包含 qianapp(不应该,但防御性测试)
        let decision = simulateDecision(
            urlString: "qianapp://callback?action=sign&result=success",
            externalSchemes: ["qianapp", "weixin"]
        )

        XCTAssertEqual(decision, .cancel)
        // 应该走 SchemeInterceptor 而非 ExternalSchemeHandler
        // SchemeInterceptor.handle 返回 true,不会走到 ExternalSchemeHandler
    }

    // MARK: - 优先级验证: http 优先于 externalSchemes

    func test_http_takes_priority_over_externalSchemes() {
        // 即使 externalSchemes 包含 http(不应该,但防御性测试)
        let decision = simulateDecision(
            urlString: "http://example.com",
            externalSchemes: ["http", "weixin"]
        )

        XCTAssertEqual(decision, .allow, "http 应放行,不走 externalSchemes")
    }

    // MARK: - scheme 大小写不敏感

    func test_external_scheme_case_insensitive() {
        let decision = simulateDecision(
            urlString: "WEIXIN://wxa/test",
            externalSchemes: ["weixin"]
        )

        XCTAssertEqual(decision, .cancel)
    }
}
