//
//  PaymentViewModelTests.swift
//  suixinkanTests
//
//  Created by Codex on 2026/6/22.
//

import XCTest
@testable import suixinkan

@MainActor
/// 收款模块测试，覆盖收款码、动态二维码、轮询和记录分组。
final class PaymentViewModelTests: XCTestCase {
    /// 测试无景区时清空二维码且不请求接口。
    func testLoadPayCodeWithoutScenicClearsState() async {
        let api = PaymentMockAPI()
        let viewModel = PaymentCollectionViewModel()
        viewModel.staticPayUrl = "old"

        await viewModel.loadPayCode(api: api, scenicId: nil)

        XCTAssertTrue(viewModel.staticPayUrl.isEmpty)
        XCTAssertEqual(api.payCodeScenicIds, [])
        XCTAssertEqual(viewModel.errorMessage, "请先选择景区")
    }

    /// 测试收款码接口加载后生成当前二维码 URL。
    func testLoadPayCodeUsesScenicIdAndAppliesStaticURL() async {
        let api = PaymentMockAPI()
        api.payCodeResponse = PayCodeResponse(staticPayUrl: "https://pay.example.com/static", dynamicPayUrl: "https://pay.example.com/dynamic")
        let viewModel = PaymentCollectionViewModel()

        await viewModel.loadPayCode(api: api, scenicId: 9)

        XCTAssertEqual(api.payCodeScenicIds, [9])
        XCTAssertEqual(viewModel.currentPayUrl, "https://pay.example.com/static")
        XCTAssertTrue(viewModel.hasStaticPayCode)
    }

    /// 测试设置金额会校验金额并生成动态收款 URL。
    func testApplyDynamicAmountBuildsDynamicURL() async {
        let viewModel = PaymentCollectionViewModel()
        viewModel.dynamicPayUrl = "https://pay.example.com/dynamic?scenic_id=9"
        viewModel.amountText = "12.345"
        viewModel.remarkText = "茶水"

        XCTAssertTrue(viewModel.applyDynamicAmount())
        XCTAssertTrue(viewModel.currentPayUrl.contains("amount=12.35"))
        XCTAssertTrue(viewModel.currentPayUrl.contains("remark="))
        XCTAssertEqual(viewModel.status, .waiting)
    }

    /// 测试轮询命中新收款记录后进入成功态。
    func testPollingFindsNewPaymentRecord() async {
        let api = PaymentMockAPI()
        api.recordResponses = [
            PaymentCollectionRecordResponse(list: [
                PaymentCollectionRecordItem(orderNumber: "A", userPhone: "138", orderAmount: "8.00", createDate: "2026-06-22", createTime: "10:00")
            ]),
            PaymentCollectionRecordResponse(list: [
                PaymentCollectionRecordItem(orderNumber: "A", userPhone: "138", orderAmount: "8.00", createDate: "2026-06-22", createTime: "10:00"),
                PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.35", createDate: "2026-06-22", createTime: "10:01")
            ])
        ]
        let viewModel = PaymentCollectionViewModel()
        viewModel.amountText = "12.35"
        await viewModel.primePaymentRecords(api: api, scenicId: 9)

        await viewModel.pollUntilPaymentDetected(api: api, scenicId: 9, maxAttempts: 1, intervalNanoseconds: 0)

        XCTAssertEqual(viewModel.status, .success(PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.35", createDate: "2026-06-22", createTime: "10:01")))
    }

    /// 测试后端缺少日汇总时能从列表兜底生成分组。
    func testRecordGroupsFallbackFromList() {
        let response = PaymentCollectionRecordResponse(list: [
            PaymentCollectionRecordItem(orderNumber: "A", userPhone: "138", orderAmount: "8.00", createDate: "2026-06-21", createTime: "10:00"),
            PaymentCollectionRecordItem(orderNumber: "B", userPhone: "139", orderAmount: "12.00", createDate: "2026-06-21", createTime: "10:01")
        ])

        let groups = PaymentCollectionRecordViewModel.makeGroups(from: response)

        XCTAssertEqual(groups.count, 1)
        XCTAssertEqual(groups[0].analyse.orderCount, 2)
        XCTAssertEqual(groups[0].analyse.orderAmountSum, "20")
    }
}

@MainActor
/// 收款 API 测试替身。
private final class PaymentMockAPI: PaymentServing {
    var payCodeResponse = PayCodeResponse()
    var recordResponses: [PaymentCollectionRecordResponse] = []
    private(set) var payCodeScenicIds: [Int] = []
    private(set) var recordScenicIds: [Int] = []

    /// 记录景区 ID 并返回收款码响应。
    func payCode(scenicId: Int) async throws -> PayCodeResponse {
        payCodeScenicIds.append(scenicId)
        return payCodeResponse
    }

    /// 记录景区 ID 并返回下一份收款记录响应。
    func paymentCollectionRecords(scenicId: Int) async throws -> PaymentCollectionRecordResponse {
        recordScenicIds.append(scenicId)
        return recordResponses.isEmpty ? PaymentCollectionRecordResponse() : recordResponses.removeFirst()
    }
}
