//
//  QueueManagementViewModelTests.swift
//  suixinkanTests
//
//  Created by Codex on 2026/6/25.
//

import XCTest
@testable import suixinkan

@MainActor
/// 排队管理列表测试，覆盖点位门禁、列表分页和动作。
final class QueueManagementViewModelTests: XCTestCase {
    override func setUp() {
        super.setUp()
        clearQueueDefaults()
    }

    func testReloadWithoutScenicClearsState() async {
        let api = ScenicQueueMock()
        let viewModel = QueueManagementViewModel()

        await viewModel.reload(api: api, scenicId: nil, userId: "77", spots: sampleSpots)

        XCTAssertTrue(viewModel.items.isEmpty)
        XCTAssertNil(viewModel.selectedSpotId)
        XCTAssertFalse(viewModel.queueGatePassed)
        XCTAssertEqual(viewModel.queueCount, 0)
    }

    func testReloadWithoutSavedSpotShowsGateAndDoesNotRequestQueue() async {
        let api = ScenicQueueMock()
        let viewModel = QueueManagementViewModel()

        await viewModel.reload(api: api, scenicId: 88, userId: "77", spots: sampleSpots)

        XCTAssertEqual(viewModel.scenicSpots.map(\.id), [901, 902])
        XCTAssertNil(viewModel.selectedSpotId)
        XCTAssertFalse(viewModel.queueGatePassed)
        XCTAssertTrue(api.homeRequests.isEmpty)
        XCTAssertTrue(api.statsRequests.isEmpty)
    }

    func testReloadWithSavedSpotLoadsHomeAndStats() async {
        saveSelectedSpot()
        let api = ScenicQueueMock()
        api.homeResponses = [ScenicQueueHomeData(list: ScenicQueueHomeListBlock(list: [ticket(id: 1001), ticket(id: 1002)], total: 2), time: "2026-06-25 10:01")]
        api.statsResponse = ScenicQueueStatsData(queueCount: 5, avgWaitMin: 12.5, time: "2026-06-25 10:02")
        let viewModel = QueueManagementViewModel()

        await viewModel.reload(api: api, scenicId: 88, userId: "77", spots: sampleSpots)

        XCTAssertEqual(viewModel.selectedSpotId, 901)
        XCTAssertTrue(viewModel.queueGatePassed)
        XCTAssertEqual(viewModel.items.map(\.id), [1001, 1002])
        XCTAssertEqual(viewModel.queueCount, 5)
        XCTAssertEqual(viewModel.avgWaitMin, 12.5)
        XCTAssertEqual(viewModel.lastSyncTimeText, "2026-06-25 10:02")
        XCTAssertEqual(api.homeRequests.first?.type, QueueListType.queueing.rawValue)
    }

    func testStatsFailureKeepsQueueList() async {
        saveSelectedSpot()
        let api = ScenicQueueMock()
        api.homeResponses = [ScenicQueueHomeData(list: ScenicQueueHomeListBlock(list: [ticket(id: 1001)], total: 1), time: "home-time")]
        api.statsError = QueueTestError.sample
        let viewModel = QueueManagementViewModel()

        await viewModel.reload(api: api, scenicId: 88, userId: "77", spots: sampleSpots)

        XCTAssertEqual(viewModel.items.map(\.id), [1001])
        XCTAssertEqual(viewModel.queueCount, 0)
        XCTAssertEqual(viewModel.lastSyncTimeText, "home-time")
    }

    func testCallPassFinishRequeueAndUserMark() async throws {
        saveSelectedSpot()
        let api = ScenicQueueMock()
        api.homeResponses = Array(repeating: ScenicQueueHomeData(list: ScenicQueueHomeListBlock(list: [ticket(id: 1001)], total: 1)), count: 8)
        let viewModel = QueueManagementViewModel()
        await viewModel.reload(api: api, scenicId: 88, userId: "77", spots: sampleSpots)

        try await viewModel.callQueue(api: api, id: 1001)
        XCTAssertEqual(api.callIds, [1001])
        XCTAssertEqual(viewModel.items.first?.isCalled, 1)

        try await viewModel.passQueue(api: api, scenicId: 88, userId: "77", id: 1001)
        XCTAssertEqual(api.passIds, [1001])

        try await viewModel.finishQueue(api: api, scenicId: 88, userId: "77", id: 1001)
        XCTAssertEqual(api.finishIds, [1001])

        try await viewModel.requeue(api: api, scenicId: 88, userId: "77", id: 1001, operatorId: 77)
        XCTAssertEqual(api.requeueRequests.map(\.recordId), [1001])
        XCTAssertEqual(api.requeueRequests.map(\.operatorId), [77])
        XCTAssertEqual(viewModel.selectedListType, .queueing)

        try await viewModel.userMark(
            api: api,
            scenicId: 88,
            userId: "77",
            request: ScenicQueueUserMarkRequest(uid: 7001, scenicId: 88, markAsFreelancePhotog: 1, queueBanDays: 14, operatorId: 77)
        )
        XCTAssertEqual(api.userMarkRequests.first?.uid, 7001)
        XCTAssertEqual(api.userMarkRequests.first?.queueBanDays, 14)
    }

    func testSocketSubscriptionAndParse() {
        XCTAssertEqual(ScenicQueueSocketClient.subscriptionPayloads(scenicSpotId: 901), [
            #"{"type":306,"params":{"scenic_spot_id":901}}"#,
            #"{"type":307,"params":{"scenic_spot_id":901}}"#
        ])

        let message = ScenicQueueSocketClient.parseMessage("""
        {"code":100000,"data":{"action":"scenic_queue_ticket_called","params":{"scenic_spot_id":"901","record_id":"1002","operator_uid":"77","event_id":"evt-1"}}}
        """)

        XCTAssertEqual(message?.code, 100000)
        XCTAssertTrue(message?.isScenicQueueEvent == true)
        XCTAssertEqual(message?.data?.params?.scenicSpotId, 901)
        XCTAssertEqual(message?.data?.params?.recordId, 1002)
        XCTAssertEqual(message?.data?.params?.operatorUid, 77)
        XCTAssertEqual(message?.data?.params?.eventId, "evt-1")
    }
}

private let sampleSpots = [
    ScenicSpotItem(id: 901, name: "东门打卡点"),
    ScenicSpotItem(id: 902, name: "西门打卡点")
]

private func saveSelectedSpot() {
    ScenicQueueSettingsStore.saveSelectedSpot(id: 901, name: "东门打卡点", userId: "77", scenicId: 88)
}

private func clearQueueDefaults() {
    let defaults = UserDefaults.standard
    [
        ScenicQueueLocalSettings.selectedSpotIdKey,
        ScenicQueueLocalSettings.selectedSpotNameKey,
        ScenicQueueSettingsStore.scopedKey(base: ScenicQueueLocalSettings.selectedSpotIdKey, userId: "77", scenicId: 88),
        ScenicQueueSettingsStore.scopedKey(base: ScenicQueueLocalSettings.selectedSpotNameKey, userId: "77", scenicId: 88),
        ScenicQueueSettingsStore.scopedSpotKey(base: ScenicQueueLocalSettings.settingsSnapshotKey, userId: "77", scenicId: 88, spotId: 901)
    ].forEach(defaults.removeObject)
}

private func ticket(id: Int64, isCalled: Int = 0) -> ScenicQueueTicket {
    ScenicQueueTicket(
        id: id,
        queueCode: "A\(id)",
        mobile: "13800000000",
        status: 0,
        statusText: "",
        waitMin: 5,
        aheadCount: 1,
        isCalled: isCalled,
        createdAt: "2026-06-25 10:00:00",
        calledAt: "",
        expiredAt: "",
        finishedAt: "",
        queueCountToday: 2,
        uid: 7001
    )
}

@MainActor
final class ScenicQueueMock: ScenicQueueServing {
    var homeResponses: [ScenicQueueHomeData] = []
    var statsResponse = ScenicQueueStatsData()
    var statsError: Error?
    var settingResponse = ScenicQueueSettingData()
    var callIds: [Int64] = []
    var passIds: [Int64] = []
    var finishIds: [Int64] = []
    var requeueRequests: [(recordId: Int64, operatorId: Int)] = []
    var userMarkRequests: [ScenicQueueUserMarkRequest] = []
    var saveSettingRequests: [ScenicQueueSaveSettingRequest] = []
    var homeRequests: [(scenicId: Int, scenicSpotId: Int, type: Int, page: Int, pageSize: Int)] = []
    var statsRequests: [(scenicId: Int, scenicSpotId: Int)] = []
    var qrcodeURL = "https://example.com/qrcode.png"
    var logs = ScenicQueueSettingChangeLogData()

    func scenicQueueStats(scenicId: Int, scenicSpotId: Int) async throws -> ScenicQueueStatsData {
        statsRequests.append((scenicId, scenicSpotId))
        if let statsError { throw statsError }
        return statsResponse
    }

    func scenicQueueHome(scenicId: Int, scenicSpotId: Int, type: Int, page: Int, pageSize: Int) async throws -> ScenicQueueHomeData {
        homeRequests.append((scenicId, scenicSpotId, type, page, pageSize))
        return homeResponses.isEmpty ? ScenicQueueHomeData() : homeResponses.removeFirst()
    }

    func socketToken() async throws -> SocketTokenResponse { SocketTokenResponse(socketToken: "token") }
    func scenicQueueCall(id: Int64) async throws -> ScenicQueueCallData {
        callIds.append(id)
        return ScenicQueueCallData(id: id, status: 1, statusText: "已叫号")
    }
    func scenicQueuePass(id: Int64) async throws -> ScenicQueuePassData {
        passIds.append(id)
        return ScenicQueuePassData(id: id)
    }
    func scenicQueueFinish(id: Int64) async throws -> ScenicQueueFinishData {
        finishIds.append(id)
        return ScenicQueueFinishData(id: id)
    }
    func scenicQueueRequeueInsertBefore(recordId: Int64, operatorId: Int) async throws {
        requeueRequests.append((recordId, operatorId))
    }
    func scenicQueueUserMark(_ request: ScenicQueueUserMarkRequest) async throws {
        userMarkRequests.append(request)
    }
    func scenicQueueSetting(scenicId: Int, scenicSpotId: Int?) async throws -> ScenicQueueSettingData { settingResponse }
    func scenicQueueSaveSetting(_ request: ScenicQueueSaveSettingRequest) async throws {
        saveSettingRequests.append(request)
    }
    func scenicQueueShootQueueQRCode(scenicId: Int, scenicSpotId: Int) async throws -> ScenicQueueShootQueueQRCodeData {
        ScenicQueueShootQueueQRCodeData(qrcodeUrl: qrcodeURL)
    }
    func scenicQueueSettingChangeLog(scenicId: Int, scenicSpotId: Int?, page: Int, pageSize: Int) async throws -> ScenicQueueSettingChangeLogData {
        logs
    }
}

private enum QueueTestError: Error {
    case sample
}
