//
//  PushNotificationTests.swift
//  suixinkanTests
//
//  Created by Codex on 2026/6/26.
//

import Foundation
import XCTest
@testable import suixinkan

/// 推送链路测试，覆盖 APNs token、payload 解析、token 上报和通知路由。
final class PushNotificationTests: XCTestCase {
    func testAPNsDeviceTokenUsesLowercaseHexString() {
        let token = APNsDeviceToken.hexString(from: Data([0x00, 0x0f, 0x10, 0xab, 0xff]))

        XCTAssertEqual(token, "000f10abff")
    }

    func testPushPayloadRoutesTopLevelFields() {
        XCTAssertEqual(PushPayload(userInfo: ["type": "1"]).route, .payment)
        XCTAssertEqual(PushPayload(userInfo: ["route": "verification_order"]).route, .verificationOrder)
        XCTAssertEqual(PushPayload(userInfo: ["uri": "/scenic-queue"]).route, .queue)
        XCTAssertEqual(PushPayload(userInfo: ["action": "task_management"]).route, .task)
        XCTAssertEqual(PushPayload(userInfo: ["route": "photographer_orders"]).route, .order)
    }

    func testPushPayloadRoutesNestedJSONString() {
        let payload = PushPayload(userInfo: [
            "extras": #"{"data":{"uri":"message_center","title":"系统消息"}}"#
        ])

        XCTAssertEqual(payload.route, .messageCenter)
    }

    func testPushPayloadRoutesNestedDictionaryAndChineseKeywords() {
        let payload = PushPayload(userInfo: [
            "data": [
                "extra": [
                    "action": "叫号提醒"
                ]
            ]
        ])

        XCTAssertEqual(payload.route, .queue)
    }

    func testPushPayloadFallsBackToMessageCenter() {
        let payload = PushPayload(userInfo: ["title": "系统消息"])

        XCTAssertEqual(payload.route, .messageCenter)
    }

    func testRegisterPushTokenUsesCurrentBackendEndpointAndLegacyQueryName() async throws {
        let session = PushRecordingURLSession()
        let api = PushAPI(client: APIClient(session: session))

        try await api.registerJPushId("000f10abff")

        let request = try XCTUnwrap(session.requests.first)
        XCTAssertEqual(request.httpMethod, "POST")
        XCTAssertEqual(request.url?.path, "/api/app/user/register-jpush-id")
        XCTAssertEqual(queryItems(from: request)["jpush_reg_id"], "000f10abff")
    }

    func testPushManagerRoutesHomeDestinationsThroughHomeNavigationStack() {
        let router = AppRouter()
        let manager = pushManager(router: router)

        manager.handleRemoteNotification(PushPayload(userInfo: ["route": "payment_qr"]))
        XCTAssertEqual(router.selectedTab, .home)
        XCTAssertEqual(router.router(for: .home).path, [.home(.paymentCollection)])

        router.router(for: .home).reset()
        manager.handleRemoteNotification(PushPayload(userInfo: ["route": "task_management"]))
        XCTAssertEqual(router.router(for: .home).path, [.home(.taskManagement)])

        router.router(for: .home).reset()
        manager.handleRemoteNotification(PushPayload(userInfo: ["uri": "/scenic-queue"]))
        XCTAssertEqual(router.router(for: .home).path, [.home(.queueManagement)])

        router.router(for: .home).reset()
        manager.handleRemoteNotification(PushPayload(userInfo: ["title": "未知通知"]))
        XCTAssertEqual(router.router(for: .home).path, [.home(.messageCenter)])
    }

    func testPushManagerRoutesOrdersEntry() {
        let router = AppRouter()
        let manager = pushManager(router: router)

        manager.handleRemoteNotification(PushPayload(userInfo: ["route": "photographer_orders"]))
        XCTAssertEqual(router.selectedTab, .orders)
        XCTAssertEqual(router.selectedOrdersEntry, .storeOrders)

        manager.handleRemoteNotification(PushPayload(userInfo: ["route": "verification_order"]))
        XCTAssertEqual(router.selectedTab, .orders)
        XCTAssertEqual(router.router(for: .orders).path.last, .orders(.writeOffList))
    }

    private func pushManager(router: AppRouter) -> PushNotificationManager {
        let session = AppSession()
        let manager = PushNotificationManager()
        manager.configure(
            api: PushAPI(client: APIClient(session: PushRecordingURLSession())),
            session: session,
            router: router
        )
        return manager
    }
}

private final class PushRecordingURLSession: URLSessionProtocol {
    private(set) var requests: [URLRequest] = []

    func data(for request: URLRequest) async throws -> (Data, URLResponse) {
        requests.append(request)
        let data = #"{"code":100000,"msg":"success","data":{}}"#.data(using: .utf8)!
        let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: nil)!
        return (data, response)
    }
}

private func queryItems(from request: URLRequest) -> [String: String] {
    guard let url = request.url,
          let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
    else {
        return [:]
    }
    return Dictionary(uniqueKeysWithValues: (components.queryItems ?? []).compactMap { item in
        guard let value = item.value else { return nil }
        return (item.name, value)
    })
}
