//
//  StoreAdminOrderListViewModelTests.swift
//  suixinkanTests
//
//  Created by Codex on 2026/6/29.
//

import XCTest
@testable import suixinkan

@MainActor
/// 店长订单 Tab ViewModel 测试，覆盖列表分页、筛选与退款参数。
final class StoreAdminOrderListViewModelTests: XCTestCase {
    /// 测试无门店时清空列表且不请求接口。
    func testReloadClearsDataWhenStoreIsMissing() async throws {
        let api = StoreAdminMockOrderService()
        api.responses = [try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")]
        let viewModel = StoreAdminOrderListViewModel()

        try await viewModel.reload(api: api, storeId: 66)
        XCTAssertFalse(viewModel.orders.isEmpty)

        try await viewModel.reload(api: api, storeId: nil)

        XCTAssertTrue(viewModel.orders.isEmpty)
        XCTAssertEqual(viewModel.total, 0)
        XCTAssertEqual(viewModel.page, 1)
        XCTAssertFalse(viewModel.hasMore)
        XCTAssertEqual(api.listCalls.count, 1)
    }

    /// 测试筛选参数会传入服务层并重置到第一页。
    func testReloadUsesFilterQueries() async throws {
        let api = StoreAdminMockOrderService()
        api.responses = [
            try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success"),
            try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")
        ]
        let viewModel = StoreAdminOrderListViewModel()
        viewModel.searchPhone = " 13800000000 "
        viewModel.selectedStatus = 30
        viewModel.filterStartDate = Self.date("2026-05-01")
        viewModel.filterEndDate = Self.date("2026-05-31")

        try await viewModel.reload(api: api, storeId: 66)
        XCTAssertEqual(api.listCalls.count, 1)
        XCTAssertEqual(api.listCalls[0].storeId, 66)
        XCTAssertEqual(api.listCalls[0].userPhone, "13800000000")
        XCTAssertEqual(api.listCalls[0].orderStatus, 30)
        XCTAssertEqual(api.listCalls[0].startTime, "2026-05-01")
        XCTAssertEqual(api.listCalls[0].endTime, "2026-05-31")

        viewModel.selectedStatus = 0
        viewModel.searchPhone = ""
        try await viewModel.reload(api: api, storeId: 66)
        XCTAssertEqual(api.listCalls[1].orderStatus, nil)
        XCTAssertNil(api.listCalls[1].userPhone)
    }

    /// 测试最后一页不会继续请求。
    func testLoadMoreStopsAfterLastPage() async throws {
        let api = StoreAdminMockOrderService()
        api.responses = [try TestFixture.payload(ListPayload<StoreOrderListItem>.self, named: "store_order_list_success")]
        let viewModel = StoreAdminOrderListViewModel()

        try await viewModel.reload(api: api, storeId: 66)
        try await viewModel.loadMore(api: api, storeId: 66)

        XCTAssertEqual(viewModel.orders.count, 2)
        XCTAssertEqual(api.listCalls.count, 1)
        XCTAssertFalse(viewModel.hasMore)
    }

  /// 测试店长退款会传入门店与订单参数。
    func testRefundUsesStoreParameters() async throws {
        let api = StoreAdminMockOrderService()
        let viewModel = StoreAdminOrderListViewModel()

        try await viewModel.refund(
            api: api,
            storeId: 66,
            orderNumber: "STORE001",
            mode: .partial,
            amount: "50.00",
            reason: "部分退款"
        )

        XCTAssertEqual(api.refundCalls.count, 1)
        XCTAssertEqual(api.refundCalls[0].storeId, 66)
        XCTAssertEqual(api.refundCalls[0].orderNumber, "STORE001")
        XCTAssertEqual(api.refundCalls[0].mode, .partial)
        XCTAssertEqual(api.refundCalls[0].amount, "50.00")
        XCTAssertEqual(api.refundCalls[0].reason, "部分退款")
    }

    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
/// 店长订单列表测试替身，记录 storeOrderList 与退款调用。
private final class StoreAdminMockOrderService: OrderServing {
    struct ListCall: Equatable {
        let storeId: Int?
        let page: Int
        let userPhone: String?
        let orderStatus: Int?
        let startTime: String?
        let endTime: String?
    }

    struct RefundCall: Equatable {
        let storeId: Int
        let orderNumber: String
        let mode: OrderRefundMode
        let amount: String
        let reason: String
    }

    var responses: [ListPayload<StoreOrderListItem>] = []
    private(set) var listCalls: [ListCall] = []
    private(set) var refundCalls: [RefundCall] = []

    func orderList(
        scenicId: Int,
        page: Int,
        pageSize: Int,
        orderStatus: Int?,
        userPhone: String?,
        startTime: String?,
        endTime: String?,
        isRefined: Int?,
        isScenicAdmin: Bool
    ) async throws -> ListPayload<OrderEntity> {
        ListPayload(total: 0, list: [])
    }

    func writeOffList(
        scenicId: Int,
        storeId: Int?,
        page: Int,
        pageSize: Int,
        orderStatus: Int?,
        orderVerifyStatus: Int?,
        startTime: String?,
        endTime: String?,
        userPhone: String?
    ) async throws -> ListPayload<WriteOffOrderItem> {
        ListPayload(total: 0, list: [])
    }

    func updateRefinedChoice(orderNumber: String, isRefined: Int) async throws {}
    func cancelOrder(orderNumber: String) async throws {}
    func sendGift(orderNumber: String, photoNum: Int, videoNum: Int) async throws {}

    func storeOrderList(
        storeId: Int?,
        page: Int,
        pageSize: Int,
        userPhone: String?,
        orderStatus: Int?,
        orderType: Int?,
        startTime: String?,
        endTime: String?
    ) async throws -> ListPayload<StoreOrderListItem> {
        listCalls.append(
            ListCall(
                storeId: storeId,
                page: page,
                userPhone: userPhone,
                orderStatus: orderStatus,
                startTime: startTime,
                endTime: endTime
            )
        )
        return responses.isEmpty ? ListPayload(total: 0, list: []) : responses.removeFirst()
    }

    func storeOrderRefund(
        storeId: Int,
        orderNumber: String,
        refundType: OrderRefundMode,
        refundAmount: String,
        refundReason: String
    ) async throws {
        refundCalls.append(
            RefundCall(
                storeId: storeId,
                orderNumber: orderNumber,
                mode: refundType,
                amount: refundAmount,
                reason: refundReason
            )
        )
    }

    func writeOff(orderNumber: String) async throws {}
    func storeOrderDetail(storeId: Int, orderNumber: String) async throws -> StoreOrderDetailResponse {
        try TestFixture.payload(StoreOrderDetailResponse.self, named: "store_order_detail_success")
    }
    func depositOrderList(scenicId: Int, page: Int, pageSize: Int) async throws -> ListPayload<DepositOrderListItem> {
        ListPayload(total: 0, list: [])
    }
    func depositOrderWriteOff(orderNumber: String) async throws {}
    func depositOrderRefund(orderNumber: String, refundReason: String) async throws {}
    func storeOrderShootingDetail(
        storeId: Int,
        orderNumber: String,
        scenicSpotId: Int,
        photogUid: Int
    ) async throws -> StoreOrderShootingDetailResponse {
        try TestFixture.payload(StoreOrderShootingDetailResponse.self, named: "store_order_shooting_detail_success")
    }
    func orderRefund(orderNumber: String, refundType: OrderRefundMode, refundAmount: String, refundReason: String) async throws {}
    func multiTravelShootHistory(orderNumber: String) async throws -> MultiTravelShootHistoryResponse {
        try TestFixture.payload(MultiTravelShootHistoryResponse.self, named: "multi_travel_shoot_history_success")
    }
    func multiTravelVerifiedScenicSpotList(orderNumber: String) async throws -> [MultiTravelVerifiedScenicSpotItem] { [] }
    func multiTravelUploadMaterial(_ request: MultiTravelUploadMaterialRequest) async throws {}
}
