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

import XCTest
@testable import suixinkan

@MainActor
/// 订单 ViewModel 测试，覆盖列表分页、筛选条件和手动核销状态。
final class OrdersViewModelTests: XCTestCase {
    /// 测试无景区时清空订单管理旧数据且不请求接口。
    func testStoreOrdersClearStaleDataWhenScenicIsMissing() async throws {
        let api = MockOrderService()
        api.storeResponses = [try TestFixture.payload(ListPayload<OrderEntity>.self, named: "order_v2_success")]
        let viewModel = OrdersViewModel()

        try await viewModel.reloadStoreOrders(api: api, scenicId: 88, roleId: nil)
        XCTAssertFalse(viewModel.storeOrders.isEmpty)

        try await viewModel.reloadStoreOrders(api: api, scenicId: nil, roleId: nil)

        XCTAssertTrue(viewModel.storeOrders.isEmpty)
        XCTAssertEqual(viewModel.storeTotal, 0)
        XCTAssertEqual(viewModel.storePage, 1)
        XCTAssertFalse(viewModel.storeHasMore)
        XCTAssertEqual(api.orderListCalls.count, 1)
    }

    /// 测试订单管理最后一页不会继续请求。
    func testStoreOrdersDoNotRequestMoreAfterLastPage() async throws {
        let api = MockOrderService()
        api.storeResponses = [try TestFixture.payload(ListPayload<OrderEntity>.self, named: "order_v2_success")]
        let viewModel = OrdersViewModel()

        try await viewModel.reloadStoreOrders(api: api, scenicId: 88, roleId: nil)
        try await viewModel.loadMoreStoreOrders(api: api, scenicId: 88, roleId: nil)

        XCTAssertEqual(viewModel.storeOrders.count, 2)
        XCTAssertEqual(viewModel.storeTotal, 2)
        XCTAssertEqual(viewModel.storePage, 1)
        XCTAssertFalse(viewModel.storeHasMore)
        XCTAssertEqual(api.orderListCalls.count, 1)
    }

    /// 测试订单管理筛选参数会传入服务层并重置到第一页。
    func testStoreOrderFilterUsesExpectedQueries() async throws {
        let api = MockOrderService()
        api.storeResponses = [
            try TestFixture.payload(ListPayload<OrderEntity>.self, named: "order_v2_success"),
            try TestFixture.payload(ListPayload<OrderEntity>.self, named: "order_v2_success")
        ]
        let viewModel = OrdersViewModel()
        viewModel.selectedStatus = -1
        viewModel.searchPhone = " 13800000000 "
        viewModel.filterStartDate = Self.date("2026-05-01")
        viewModel.filterEndDate = Self.date("2026-05-31")

        try await viewModel.reloadStoreOrders(api: api, scenicId: 88, roleId: nil)

        viewModel.selectedStatus = 30
        viewModel.searchPhone = " "
        viewModel.filterStartDate = nil
        viewModel.filterEndDate = nil
        try await viewModel.reloadStoreOrders(api: api, scenicId: 88, roleId: 53)

        XCTAssertEqual(api.orderListCalls.count, 2)
        XCTAssertEqual(api.orderListCalls[0].page, 1)
        XCTAssertEqual(api.orderListCalls[0].isRefined, 1)
        XCTAssertEqual(api.orderListCalls[0].userPhone, "13800000000")
        XCTAssertNil(api.orderListCalls[0].orderStatus)
        XCTAssertEqual(api.orderListCalls[0].startTime, "2026-05-01")
        XCTAssertEqual(api.orderListCalls[0].endTime, "2026-05-31")
        XCTAssertFalse(api.orderListCalls[0].isScenicAdmin)
        XCTAssertEqual(api.orderListCalls[1].orderStatus, 30)
        XCTAssertNil(api.orderListCalls[1].isRefined)
        XCTAssertNil(api.orderListCalls[1].userPhone)
        XCTAssertTrue(api.orderListCalls[1].isScenicAdmin)
        XCTAssertEqual(viewModel.storePage, 1)
    }

    /// 测试无景区时清空核销订单旧数据且不请求接口。
    func testWriteOffOrdersClearStaleDataWhenScenicIsMissing() async throws {
        let api = MockOrderService()
        api.writeOffResponses = [try TestFixture.payload(ListPayload<WriteOffOrderItem>.self, named: "writeoff_list_success")]
        let viewModel = OrdersViewModel()

        try await viewModel.reloadWriteOffOrders(api: api, scenicId: 88, storeId: 66)
        XCTAssertFalse(viewModel.writeOffOrders.isEmpty)

        try await viewModel.reloadWriteOffOrders(api: api, scenicId: nil)

        XCTAssertTrue(viewModel.writeOffOrders.isEmpty)
        XCTAssertEqual(viewModel.writeOffTotal, 0)
        XCTAssertEqual(viewModel.writeOffPage, 1)
        XCTAssertFalse(viewModel.writeOffHasMore)
        XCTAssertEqual(api.writeOffListCalls.count, 1)
        XCTAssertEqual(api.writeOffListCalls[0].storeId, 66)
    }

    /// 测试核销订单最后一页不会继续请求。
    func testWriteOffOrdersDoNotRequestMoreAfterLastPage() async throws {
        let api = MockOrderService()
        api.writeOffResponses = [try TestFixture.payload(ListPayload<WriteOffOrderItem>.self, named: "writeoff_list_success")]
        let viewModel = OrdersViewModel()

        try await viewModel.reloadWriteOffOrders(api: api, scenicId: 88)
        try await viewModel.loadMoreWriteOffOrders(api: api, scenicId: 88)

        XCTAssertEqual(viewModel.writeOffOrders.count, 1)
        XCTAssertEqual(viewModel.writeOffTotal, 1)
        XCTAssertEqual(viewModel.writeOffPage, 1)
        XCTAssertFalse(viewModel.writeOffHasMore)
        XCTAssertEqual(api.writeOffListCalls.count, 1)
    }

    /// 测试核销成功后刷新列表并重置分页状态。
    func testVerifyRefreshesWriteOffOrdersAndResetsPagingState() async throws {
        let api = MockOrderService()
        api.writeOffResponses = [try TestFixture.payload(ListPayload<WriteOffOrderItem>.self, named: "writeoff_list_success")]
        let viewModel = OrdersViewModel()

        try await viewModel.verify(api: api, scenicId: 88, storeId: 66, orderNumber: "VERIFY001")

        XCTAssertEqual(api.verifiedOrderNumbers, ["VERIFY001"])
        XCTAssertEqual(api.writeOffListCalls.count, 1)
        XCTAssertEqual(api.writeOffListCalls[0].scenicId, 88)
        XCTAssertEqual(api.writeOffListCalls[0].storeId, 66)
        XCTAssertEqual(api.writeOffListCalls[0].page, 1)
        XCTAssertEqual(viewModel.writeOffOrders.count, 1)
        XCTAssertEqual(viewModel.writeOffPage, 1)
        XCTAssertFalse(viewModel.isVerifying)
        XCTAssertNil(viewModel.currentVerifyingOrderNumber)
    }

    /// 测试扫码命中当前核销列表时返回可高亮订单。
    func testScanResultMatchesCurrentWriteOffOrder() async throws {
        let api = MockOrderService()
        api.writeOffResponses = [try TestFixture.payload(ListPayload<WriteOffOrderItem>.self, named: "writeoff_list_success")]
        let viewModel = OrdersViewModel()
        try await viewModel.reloadWriteOffOrders(api: api, scenicId: 88)

        let result = try XCTUnwrap(viewModel.matchedWriteOffOrder(for: "https://example.com/scan?order_number=VERIFY001"))

        XCTAssertEqual(result.orderNumber, "VERIFY001")
        XCTAssertEqual(result.matched?.orderNumber, "VERIFY001")
    }

    /// 测试扫码未命中当前列表时仍保留解析出的订单号。
    func testScanResultKeepsParsedOrderNumberWhenCurrentListDoesNotContainIt() async throws {
        let api = MockOrderService()
        api.writeOffResponses = [try TestFixture.payload(ListPayload<WriteOffOrderItem>.self, named: "writeoff_list_success")]
        let viewModel = OrdersViewModel()
        try await viewModel.reloadWriteOffOrders(api: api, scenicId: 88)

        let result = try XCTUnwrap(viewModel.matchedWriteOffOrder(for: "order-number=VERIFY999"))

        XCTAssertEqual(result.orderNumber, "VERIFY999")
        XCTAssertNil(result.matched)
    }

    /// 测试重复核销提交会被忽略。
    func testVerifyIgnoresDuplicateSubmitWhileAlreadyVerifying() async throws {
        let api = MockOrderService()
        let viewModel = OrdersViewModel()
        viewModel.markVerifyingForTests(orderNumber: "VERIFY001")

        try await viewModel.verify(api: api, scenicId: 88, orderNumber: "VERIFY002")

        XCTAssertTrue(api.verifiedOrderNumbers.isEmpty)
        XCTAssertTrue(api.writeOffListCalls.isEmpty)
        XCTAssertTrue(viewModel.isVerifying)
        XCTAssertEqual(viewModel.currentVerifyingOrderNumber, "VERIFY001")
    }

    /// 测试核销失败时清理提交状态且不刷新列表。
    func testVerifyFailureClearsSubmittingStateWithoutRefreshingList() async throws {
        let api = MockOrderService()
        api.verifyError = APIError.httpStatus(500, "server error")
        let viewModel = OrdersViewModel()

        do {
            try await viewModel.verify(api: api, scenicId: 88, orderNumber: "VERIFY001")
            XCTFail("Expected verify to fail")
        } catch APIError.httpStatus(let statusCode, _) {
            XCTAssertEqual(statusCode, 500)
        } catch {
            XCTFail("Unexpected error: \(error)")
        }

        XCTAssertEqual(api.verifiedOrderNumbers, ["VERIFY001"])
        XCTAssertTrue(api.writeOffListCalls.isEmpty)
        XCTAssertFalse(viewModel.isVerifying)
        XCTAssertNil(viewModel.currentVerifyingOrderNumber)
    }

    /// 测试订单 API 会按角色选择正确接口路径。
    func testOrdersAPIUsesExpectedEndpointForRole() async throws {
        let session = RecordingURLSession(data: try TestFixture.data(named: "order_v2_success"))
        let api = OrdersAPI(client: APIClient(session: session))

        _ = try await api.orderList(
            scenicId: 88,
            page: 1,
            pageSize: 20,
            orderStatus: 30,
            userPhone: "13800000000",
            startTime: "2026-05-01",
            endTime: "2026-05-31",
            isRefined: nil,
            isScenicAdmin: true
        )

        let request = try XCTUnwrap(session.requests.first)
        XCTAssertEqual(request.url?.path, "/api/app/scenic-admin/order/list")
        let query = queryItems(from: request)
        XCTAssertEqual(query["scenic_id"], "88")
        XCTAssertEqual(query["order_status"], "30")
        XCTAssertEqual(query["user_phone"], "13800000000")
        XCTAssertEqual(query["start_time"], "2026-05-01")
        XCTAssertEqual(query["end_time"], "2026-05-31")
    }

    private static func date(_ text: String) -> Date {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter.date(from: text)!
    }
}

@MainActor
/// 订单服务测试替身，记录请求参数并返回预设响应。
private final class MockOrderService: OrderServing {
    struct OrderListCall: Equatable {
        let scenicId: Int
        let page: Int
        let pageSize: Int
        let orderStatus: Int?
        let userPhone: String?
        let startTime: String?
        let endTime: String?
        let isRefined: Int?
        let isScenicAdmin: Bool
    }

    struct WriteOffListCall: Equatable {
        let scenicId: Int
        let storeId: Int?
        let page: Int
        let pageSize: Int
    }

    var storeResponses: [ListPayload<OrderEntity>] = []
    var writeOffResponses: [ListPayload<WriteOffOrderItem>] = []
    var detailResponses: [StoreOrderDetailResponse] = []
    var verifyError: Error?
    private(set) var orderListCalls: [OrderListCall] = []
    private(set) var writeOffListCalls: [WriteOffListCall] = []
    private(set) var detailCalls: [(storeId: Int, orderNumber: String)] = []
    private(set) var verifiedOrderNumbers: [String] = []

    /// 返回测试订单管理列表。
    func orderList(
        scenicId: Int,
        page: Int,
        pageSize: Int,
        orderStatus: Int?,
        userPhone: String?,
        startTime: String?,
        endTime: String?,
        isRefined: Int?,
        isScenicAdmin: Bool
    ) async throws -> ListPayload<OrderEntity> {
        orderListCalls.append(
            OrderListCall(
                scenicId: scenicId,
                page: page,
                pageSize: pageSize,
                orderStatus: orderStatus,
                userPhone: userPhone,
                startTime: startTime,
                endTime: endTime,
                isRefined: isRefined,
                isScenicAdmin: isScenicAdmin
            )
        )
        return storeResponses.isEmpty ? ListPayload(total: 0, list: []) : storeResponses.removeFirst()
    }

    /// 返回测试核销订单列表。
    func writeOffList(scenicId: Int, storeId: Int?, page: Int, pageSize: Int) async throws -> ListPayload<WriteOffOrderItem> {
        writeOffListCalls.append(WriteOffListCall(scenicId: scenicId, storeId: storeId, page: page, pageSize: pageSize))
        return writeOffResponses.isEmpty ? ListPayload(total: 0, list: []) : writeOffResponses.removeFirst()
    }

    /// 记录测试核销订单号。
    func writeOff(orderNumber: String) async throws {
        verifiedOrderNumbers.append(orderNumber)
        if let verifyError {
            throw verifyError
        }
    }

    /// 返回测试订单详情。
    func storeOrderDetail(storeId: Int, orderNumber: String) async throws -> StoreOrderDetailResponse {
        detailCalls.append((storeId: storeId, orderNumber: orderNumber))
        return detailResponses.isEmpty ? try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success") : detailResponses.removeFirst()
    }
}

/// URLSession 测试替身，记录请求并返回固定响应。
private final class RecordingURLSession: URLSessionProtocol {
    let data: Data
    var statusCode: Int
    private(set) var requests: [URLRequest] = []

    init(data: Data, statusCode: Int = 200) {
        self.data = data
        self.statusCode = statusCode
    }

    /// 返回固定数据和 HTTP 响应。
    func data(for request: URLRequest) async throws -> (Data, URLResponse) {
        requests.append(request)
        return (
            data,
            HTTPURLResponse(url: request.url!, statusCode: statusCode, httpVersion: nil, headerFields: nil)!
        )
    }
}

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
        item.value.map { (item.name, $0) }
    })
}
