//
//  MessageCenterViewModelTests.swift
//  suixinkanTests
//

import Foundation
import XCTest
@testable import suixinkan

/// 消息中心 ViewModel 测试。
@MainActor
final class MessageCenterViewModelTests: XCTestCase {
    func testInitialLoadAndLoadMoreUseLastIdUntilNoMore() async {
        let api = MockMessageCenterAPI()
        api.listResponses = [
            MessageListResponse(hasMore: true, lastId: 8, items: [makeMessage(id: 1), makeMessage(id: 8)]),
            MessageListResponse(hasMore: false, lastId: 0, items: [makeMessage(id: 9)]),
        ]
        let viewModel = MessageCenterViewModel()

        await viewModel.loadInitial(api: api)
        await viewModel.loadMoreIfNeeded(lastVisibleIndex: 1, api: api)
        await viewModel.loadMoreIfNeeded(lastVisibleIndex: 2, api: api)

        XCTAssertEqual(api.listCalls.map(\.lastId), [0, 8])
        XCTAssertEqual(api.listCalls.map(\.limit), [20, 20])
        XCTAssertEqual(api.listCalls.map(\.unread), [0, 0])
        XCTAssertEqual(viewModel.items.map(\.id), [1, 8, 9])
        XCTAssertFalse(viewModel.canLoadMore)
    }

    func testUnreadSelectionMarksReadAndOpensDetail() async {
        let api = MockMessageCenterAPI()
        api.listResponses = [MessageListResponse(hasMore: false, lastId: 0, items: [makeMessage(id: 3, isRead: false)])]
        api.readResponse = MessageUnreadCountResponse(unreadCount: 4)
        let viewModel = MessageCenterViewModel()
        var opened: MessageItem?
        var callbackCount: Int?
        viewModel.onOpenDetail = { opened = $0 }
        viewModel.onUnreadMessageCountChange = { callbackCount = $0 }
        let unreadChanged = expectation(
            forNotification: NotificationName.unreadMessageCountDidChange,
            object: nil,
            handler: { notification in
                notification.userInfo?[NotificationUserInfoKey.unreadCount] as? Int == 4
            }
        )

        await viewModel.loadInitial(api: api)
        await viewModel.selectMessage(id: 3, api: api)
        await fulfillment(of: [unreadChanged], timeout: 1)

        XCTAssertEqual(api.readCalls, [3])
        XCTAssertEqual(opened?.id, 3)
        XCTAssertEqual(opened?.isRead, true)
        XCTAssertEqual(viewModel.items.first?.isRead, true)
        XCTAssertEqual(callbackCount, 4)
    }

    func testMarkAllAsReadUpdatesItemsAndPublishesLatestCount() async {
        let api = MockMessageCenterAPI()
        api.listResponses = [MessageListResponse(items: [
            makeMessage(id: 1, isRead: false),
            makeMessage(id: 2, isRead: true),
        ])]
        api.readAllResponse = MessageReadAllResponse(updatedCount: 1, unreadCount: 0)
        let viewModel = MessageCenterViewModel()
        var callbackCount: Int?
        var message: String?
        viewModel.onUnreadMessageCountChange = { callbackCount = $0 }
        viewModel.onShowMessage = { message = $0 }
        let unreadChanged = expectation(
            forNotification: NotificationName.unreadMessageCountDidChange,
            object: nil,
            handler: { notification in
                notification.userInfo?[NotificationUserInfoKey.unreadCount] as? Int == 0
            }
        )

        await viewModel.loadInitial(api: api)
        let response = await viewModel.markAllAsRead(api: api)
        await fulfillment(of: [unreadChanged], timeout: 1)

        XCTAssertEqual(api.readAllCallCount, 1)
        XCTAssertEqual(response, MessageReadAllResponse(updatedCount: 1, unreadCount: 0))
        XCTAssertTrue(viewModel.items.allSatisfy(\.isRead))
        XCTAssertEqual(callbackCount, 0)
        XCTAssertEqual(message, "已全部标记为已读")
    }

    func testMarkAllAsReadFailureKeepsUnreadState() async {
        let api = MockMessageCenterAPI()
        api.listResponses = [MessageListResponse(items: [makeMessage(id: 6, isRead: false)])]
        api.readAllError = TestError(message: "fail")
        let viewModel = MessageCenterViewModel()
        var message: String?
        viewModel.onShowMessage = { message = $0 }

        await viewModel.loadInitial(api: api)
        let response = await viewModel.markAllAsRead(api: api)

        XCTAssertNil(response)
        XCTAssertEqual(api.readAllCallCount, 1)
        XCTAssertEqual(viewModel.items.first?.isRead, false)
        XCTAssertEqual(message, "全部标记已读失败，请稍后重试")
    }

    func testReadSelectionOpensDetailWithoutCallingReadAPI() async {
        let api = MockMessageCenterAPI()
        api.listResponses = [MessageListResponse(hasMore: false, lastId: 0, items: [makeMessage(id: 4, isRead: true)])]
        let viewModel = MessageCenterViewModel()
        var opened: MessageItem?
        viewModel.onOpenDetail = { opened = $0 }

        await viewModel.loadInitial(api: api)
        await viewModel.selectMessage(id: 4, api: api)

        XCTAssertTrue(api.readCalls.isEmpty)
        XCTAssertEqual(opened?.id, 4)
    }

    func testUnreadSelectionFailureShowsMessageAndDoesNotOpenDetail() async {
        let api = MockMessageCenterAPI()
        api.listResponses = [MessageListResponse(hasMore: false, lastId: 0, items: [makeMessage(id: 5, isRead: false)])]
        api.readError = TestError(message: "fail")
        let viewModel = MessageCenterViewModel()
        var message: String?
        var didOpen = false
        viewModel.onShowMessage = { message = $0 }
        viewModel.onOpenDetail = { _ in didOpen = true }

        await viewModel.loadInitial(api: api)
        await viewModel.selectMessage(id: 5, api: api)

        XCTAssertEqual(message, "标记已读失败")
        XCTAssertFalse(didOpen)
        XCTAssertEqual(viewModel.items.first?.isRead, false)
    }

    func testDeleteSuccessRemovesMessageAndReportsDeletedId() async {
        let api = MockMessageCenterAPI()
        let listViewModel = MessageCenterViewModel()
        api.listResponses = [MessageListResponse(items: [makeMessage(id: 7)])]
        await listViewModel.loadInitial(api: api)

        let detailViewModel = MessageDetailViewModel(message: makeMessage(id: 7))
        var deletedId: Int?
        var message: String?
        detailViewModel.onDeleted = { deletedId = $0 }
        detailViewModel.onShowMessage = { message = $0 }

        await detailViewModel.delete(api: api)
        listViewModel.removeMessage(id: deletedId ?? 0)

        XCTAssertEqual(api.deleteCalls, [7])
        XCTAssertEqual(deletedId, 7)
        XCTAssertEqual(message, "删除成功")
        XCTAssertTrue(listViewModel.items.isEmpty)
    }

    func testDeleteInvalidIdAndFailureMessages() async {
        let api = MockMessageCenterAPI()
        let invalidViewModel = MessageDetailViewModel(message: makeMessage(id: 0))
        var invalidMessage: String?
        invalidViewModel.onShowMessage = { invalidMessage = $0 }

        await invalidViewModel.delete(api: api)

        XCTAssertEqual(invalidMessage, "消息ID无效")
        XCTAssertTrue(api.deleteCalls.isEmpty)

        api.deleteError = TestError(message: "server down")
        let failingViewModel = MessageDetailViewModel(message: makeMessage(id: 9))
        var failureMessage: String?
        failingViewModel.onShowMessage = { failureMessage = $0 }

        await failingViewModel.delete(api: api)

        XCTAssertEqual(failureMessage, "删除失败: server down")
    }
}

@MainActor
private final class MockMessageCenterAPI: MessageCenterServing {
    var listResponses: [MessageListResponse] = []
    var listCalls: [(lastId: Int, limit: Int, unread: Int)] = []
    var readCalls: [Int] = []
    var readResponse = MessageUnreadCountResponse()
    var readAllResponse = MessageReadAllResponse()
    var readAllCallCount = 0
    var deleteCalls: [Int] = []
    var readError: Error?
    var readAllError: Error?
    var deleteError: Error?

    func list(lastId: Int, limit: Int, unread: Int) async throws -> MessageListResponse {
        listCalls.append((lastId, limit, unread))
        guard !listResponses.isEmpty else { return MessageListResponse() }
        return listResponses.removeFirst()
    }

    func unreadCount() async throws -> MessageUnreadCountResponse {
        MessageUnreadCountResponse()
    }

    func markAsRead(messageId: Int) async throws -> MessageUnreadCountResponse {
        readCalls.append(messageId)
        if let readError { throw readError }
        return readResponse
    }

    func markAllAsRead() async throws -> MessageReadAllResponse {
        readAllCallCount += 1
        if let readAllError { throw readAllError }
        return readAllResponse
    }

    func delete(messageId: Int) async throws {
        deleteCalls.append(messageId)
        if let deleteError { throw deleteError }
    }
}

private struct TestError: LocalizedError {
    let message: String
    var errorDescription: String? { message }
}

private func makeMessage(id: Int, isRead: Bool = false) -> MessageItem {
    MessageItem(
        id: id,
        receiverId: 1,
        receiverType: "staff",
        type: 1,
        typeName: "系统通知",
        title: "系统通知",
        content: "消息内容",
        pushAt: "",
        isRead: isRead,
        readAt: "",
        pushChannel: 1,
        pushChannelName: "站内信",
        createdAt: "2026-07-09 10:00:00",
        updatedAt: "2026-07-09 10:00:00"
    )
}
