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

import XCTest
@testable import suixinkan

@MainActor
/// 消息中心测试，覆盖分页、筛选、已读和删除。
final class MessageCenterViewModelTests: XCTestCase {
    func testReloadFirstPageMapsMessagesAndPaginationState() async {
        let api = MessageCenterMock()
        api.listResponses = [
            MessageListResponse(hasMore: true, lastId: 101, items: [
                MessageEntity(id: 101, type: 1, typeName: "", title: "新订单提醒", content: "有新订单", pushAt: "2026-06-25 10:00", isRead: false),
                MessageEntity(id: 100, type: 2, typeName: "核销", title: "", content: "核销完成", pushAt: "2026-06-25 09:00", isRead: true)
            ])
        ]
        let viewModel = MessageCenterViewModel()
        viewModel.selectedFilter = .unread

        await viewModel.reloadFirstPage(api: api)

        XCTAssertEqual(api.listRequests.map(\.unread), [1])
        XCTAssertEqual(viewModel.messages.map(\.id), ["msg_101", "msg_100"])
        XCTAssertEqual(viewModel.messages[0].title, "新订单提醒")
        XCTAssertEqual(viewModel.messages[0].type, .order)
        XCTAssertEqual(viewModel.messages[1].title, "核销")
        XCTAssertEqual(viewModel.messages[1].type, .writeOff)
        XCTAssertEqual(viewModel.unreadCount, 1)
        XCTAssertTrue(viewModel.hasMoreMessages)
        XCTAssertEqual(viewModel.lastId, 101)
    }

    func testLoadMoreDeduplicatesSortsAndStopsAtTail() async {
        let api = MessageCenterMock()
        api.listResponses = [
            MessageListResponse(hasMore: true, lastId: 101, items: [
                MessageEntity(id: 101, type: 1, title: "A", content: "A", pushAt: "2026-06-25 10:00"),
                MessageEntity(id: 100, type: 3, title: "B", content: "B", pushAt: "2026-06-25 09:00")
            ]),
            MessageListResponse(hasMore: false, lastId: 99, items: [
                MessageEntity(id: 100, type: 3, title: "B2", content: "B2", pushAt: "2026-06-25 09:00"),
                MessageEntity(id: 99, type: 0, title: "C", content: "C", pushAt: "2026-06-24 08:00")
            ])
        ]
        let viewModel = MessageCenterViewModel()

        await viewModel.reloadFirstPage(api: api)
        await viewModel.loadMore(api: api)
        await viewModel.loadMore(api: api)

        XCTAssertEqual(api.listRequests.map(\.lastId), [0, 101])
        XCTAssertEqual(viewModel.messages.map(\.id), ["msg_101", "msg_100", "msg_99"])
        XCTAssertEqual(viewModel.messages[1].title, "B2")
        XCTAssertFalse(viewModel.hasMoreMessages)
        XCTAssertEqual(viewModel.lastId, 99)
    }

    func testReloadFailureClearsStaleMessagesAndPaging() async {
        let api = MessageCenterMock()
        api.listResponses = [
            MessageListResponse(hasMore: true, lastId: 101, items: [
                MessageEntity(id: 101, type: 1, title: "A")
            ])
        ]
        let viewModel = MessageCenterViewModel()
        await viewModel.reloadFirstPage(api: api)

        api.listError = TestError.sample
        await viewModel.reloadFirstPage(api: api)

        XCTAssertTrue(viewModel.messages.isEmpty)
        XCTAssertFalse(viewModel.hasMoreMessages)
        XCTAssertEqual(viewModel.lastId, 0)
        XCTAssertTrue(viewModel.loadFailed)
    }

    func testMarkReadAllReadAndDeleteUpdateOnlyAfterSuccess() async throws {
        let api = MessageCenterMock()
        api.listResponses = [
            MessageListResponse(items: [
                MessageEntity(id: 101, type: 1, title: "A", isRead: false),
                MessageEntity(id: 100, type: 0, title: "B", isRead: false)
            ])
        ]
        let viewModel = MessageCenterViewModel()
        await viewModel.reloadFirstPage(api: api)

        try await viewModel.markAsRead(api: api, item: try XCTUnwrap(viewModel.messages.first))
        XCTAssertEqual(api.readIds, [101])
        XCTAssertEqual(viewModel.messages.map(\.isRead), [true, false])

        api.readIds = []
        try await viewModel.markAllAsRead(api: api)
        XCTAssertEqual(api.readIds, [100])
        XCTAssertTrue(viewModel.messages.allSatisfy(\.isRead))

        let item = try XCTUnwrap(viewModel.messages.first)
        try await viewModel.deleteMessage(api: api, item: item)
        XCTAssertEqual(api.deleteIds, [101])
        XCTAssertFalse(viewModel.messages.contains(where: { $0.id == item.id }))
    }

    func testFailedReadAndDeleteKeepLocalState() async throws {
        let api = MessageCenterMock()
        api.listResponses = [MessageListResponse(items: [MessageEntity(id: 101, type: 1, title: "A", isRead: false)])]
        let viewModel = MessageCenterViewModel()
        await viewModel.reloadFirstPage(api: api)
        let item = try XCTUnwrap(viewModel.messages.first)

        api.readError = TestError.sample
        await XCTAssertThrowsErrorAsync(try await viewModel.markAsRead(api: api, item: item))
        XCTAssertFalse(try XCTUnwrap(viewModel.messages.first).isRead)

        api.readError = nil
        api.deleteError = TestError.sample
        await XCTAssertThrowsErrorAsync(try await viewModel.deleteMessage(api: api, item: item))
        XCTAssertEqual(viewModel.messages.map(\.id), [item.id])
    }
}

@MainActor
private final class MessageCenterMock: MessageCenterServing {
    var listResponses: [MessageListResponse] = []
    var listError: Error?
    var readError: Error?
    var deleteError: Error?
    var readIds: [Int] = []
    var deleteIds: [Int] = []
    private(set) var listRequests: [(lastId: Int, limit: Int, unread: Int)] = []

    func messageList(lastId: Int, limit: Int, unread: Int) async throws -> MessageListResponse {
        listRequests.append((lastId, limit, unread))
        if let listError { throw listError }
        return listResponses.isEmpty ? MessageListResponse() : listResponses.removeFirst()
    }

    func messageRead(id: Int) async throws {
        if let readError { throw readError }
        readIds.append(id)
    }

    func messageDelete(id: Int) async throws {
        if let deleteError { throw deleteError }
        deleteIds.append(id)
    }
}

private enum TestError: LocalizedError {
    case sample
    var errorDescription: String? { "测试错误" }
}

private func XCTAssertThrowsErrorAsync(
    _ expression: @autoclosure () async throws -> Void,
    file: StaticString = #filePath,
    line: UInt = #line
) async {
    do {
        try await expression()
        XCTFail("Expected error", file: file, line: line)
    } catch {}
}
