//
//  HomeViewModelTests.swift
//  suixinkanTests
//

import UIKit
import XCTest
@testable import suixinkan

@MainActor
/// 首页 ViewModel 测试。
final class HomeViewModelTests: XCTestCase {

    private var appStore: AppStore!
    private var defaults: UserDefaults!

    override func setUp() {
        defaults = UserDefaults(suiteName: "HomeViewModelTests")!
        defaults.removePersistentDomain(forName: "HomeViewModelTests")
        appStore = AppStore(defaults: defaults)
        appStore.session.userId = "2001"
        appStore.session.accountType = .scenicUser
        appStore.session.roleCode = AppRoleCode.photographer.rawValue
        appStore.session.roleName = "摄影师"
        appStore.session.currentScenicId = 10
        appStore.session.currentScenicName = "测试景区"
    }

    override func tearDown() {
        appStore.logout()
        super.tearDown()
    }

    func testMinimalTopRoleFlag() {
        appStore.session.roleCode = AppRoleCode.storeAdmin.rawValue
        let viewModel = HomeViewModel(appStore: appStore)
        viewModel.refreshLocalDisplayState()
        XCTAssertTrue(viewModel.isMinimalTopRole)

        appStore.session.roleCode = AppRoleCode.photographer.rawValue
        viewModel.refreshLocalDisplayState()
        XCTAssertFalse(viewModel.isMinimalTopRole)
    }

    func testLocationReportPromptDefaultsToFixedHomeCopy() {
        let viewModel = HomeViewModel(appStore: appStore)

        XCTAssertEqual(viewModel.locationInfoText, HomeViewModel.locationReportPromptText)
        XCTAssertEqual(viewModel.locationInfoText, "您已进入打卡范围")
    }

    func testQuickActionsOnlineEntrySyncsOfflineStyle() throws {
        let view = HomeQuickActionsView()

        view.apply(isOnline: false)

        let statusContainer = try statusActionContainer(in: view)
        XCTAssertEqual(statusContainer.backgroundColor?.homeTestHexRGB, AppColor.cardBackground.homeTestHexRGB)
        XCTAssertEqual(statusContainer.layer.borderWidth, 1)
        XCTAssertEqual(statusLabel(in: statusContainer)?.text, "离线")
        XCTAssertEqual(statusLabel(in: statusContainer)?.textColor.homeTestHexRGB, AppColor.textTabInactive.homeTestHexRGB)
        XCTAssertEqual(statusIcon(in: statusContainer)?.accessibilityIdentifier, "wifi.slash")
        XCTAssertEqual(statusIcon(in: statusContainer)?.tintColor.homeTestHexRGB, AppColor.textTabInactive.homeTestHexRGB)
    }

    func testQuickActionsOnlineEntrySyncsOnlineStyle() throws {
        let view = HomeQuickActionsView()

        view.apply(isOnline: true)

        let statusContainer = try statusActionContainer(in: view)
        XCTAssertEqual(statusContainer.backgroundColor?.homeTestHexRGB, AppColor.online.homeTestHexRGB)
        XCTAssertEqual(statusLabel(in: statusContainer)?.text, "在线")
        XCTAssertEqual(statusLabel(in: statusContainer)?.textColor.homeTestHexRGB, AppColor.successBackground.homeTestHexRGB)
        XCTAssertEqual(statusIcon(in: statusContainer)?.accessibilityIdentifier, "wifi")
        XCTAssertEqual(statusIcon(in: statusContainer)?.tintColor.homeTestHexRGB, AppColor.successBackground.homeTestHexRGB)
    }

    func testRebuildCommonMenusFiltersUnknownURIs() {
        appStore.permissions.savePermissionItems([
            HomePermissionItem(id: "1", name: "钱包", uri: "wallet"),
            HomePermissionItem(id: "2", name: "未知", uri: "unknown_uri"),
        ])
        let viewModel = HomeViewModel(appStore: appStore)
        viewModel.rebuildCommonMenus()
        XCTAssertEqual(viewModel.commonMenus.map(\.uri), ["wallet"])
    }

    func testRebuildCommonMenusPersistsDefaultURIsOnFirstLoad() {
        let menuStore = HomeCommonMenuStore(defaults: defaults)
        appStore.permissions.savePermissionItems([
            HomePermissionItem(id: "1", name: "钱包", uri: "wallet"),
            HomePermissionItem(id: "2", name: "消息", uri: "message_center"),
            HomePermissionItem(id: "3", name: "云盘", uri: "cloud_management"),
            HomePermissionItem(id: "4", name: "任务", uri: "task_management"),
            HomePermissionItem(id: "5", name: "设置", uri: "system_settings"),
        ])
        let viewModel = HomeViewModel(appStore: appStore, commonMenuStore: menuStore)

        viewModel.rebuildCommonMenus()

        XCTAssertEqual(viewModel.commonMenus.map(\.uri), [
            "wallet", "cloud_management", "task_management", "system_settings",
        ])
        XCTAssertEqual(
            menuStore.savedCommonURIs(
                accountScope: appStore.session.accountCachePrefix,
                roleCode: appStore.session.roleCode
            ),
            ["wallet", "cloud_management", "task_management", "system_settings"]
        )
    }

    func testEmptyRolePermissionShowsPermissionDialog() async throws {
        let responseData = try TestJSON.envelope(data: [RolePermissionResponse]())
        let session = MockURLSession(responses: [responseData])
        let api = HomeAPI(client: APIClient(session: session))
        let viewModel = HomeViewModel(appStore: appStore)

        await viewModel.loadPermissions(api: api, force: true)

        XCTAssertTrue(viewModel.showPermissionDialog)
        XCTAssertTrue(viewModel.commonMenus.isEmpty)
    }

    func testPermissionApplyDestinationRoutesReviewingAndRejectedToStatus() async throws {
        let reviewingJSON = """
        {"code":100000,"msg":"success","data":[{"code":"AP001","role_id":1,"role_name":"摄影师","scenic_list":[],"status":1,"status_label":"审核中","created_at":"","audit_note":"","audited_at":null,"audited_by":null}]}
        """.data(using: .utf8)!
        let rejectedJSON = """
        {"code":100000,"msg":"success","data":[{"code":"AP002","role_id":1,"role_name":"摄影师","scenic_list":[],"status":3,"status_label":"已驳回","created_at":"","audit_note":"资料不全","audited_at":null,"audited_by":null}]}
        """.data(using: .utf8)!
        let session = MockURLSession(responses: [reviewingJSON, rejectedJSON])
        let api = HomeAPI(client: APIClient(environment: .testing, session: session))
        let viewModel = HomeViewModel(appStore: appStore)

        let reviewingDestination = await viewModel.resolvePermissionApplyDestination(api: api)
        let rejectedDestination = await viewModel.resolvePermissionApplyDestination(api: api)
        XCTAssertEqual(reviewingDestination, .status(applyCode: "AP001"))
        XCTAssertEqual(rejectedDestination, .status(applyCode: "AP002"))
    }

    func testPermissionApplyDestinationFallsBackToApply() async throws {
        let completedJSON = """
        {"code":100000,"msg":"success","data":[{"code":"AP003","role_id":1,"role_name":"摄影师","scenic_list":[],"status":2,"status_label":"已通过","created_at":"","audit_note":"","audited_at":null,"audited_by":null}]}
        """.data(using: .utf8)!
        let emptyJSON = #"{"code":100000,"msg":"success","data":[]}"#.data(using: .utf8)!
        let session = MockURLSession(responses: [completedJSON, emptyJSON])
        let api = HomeAPI(client: APIClient(environment: .testing, session: session))
        let viewModel = HomeViewModel(appStore: appStore)

        let completedDestination = await viewModel.resolvePermissionApplyDestination(api: api)
        let emptyDestination = await viewModel.resolvePermissionApplyDestination(api: api)
        XCTAssertEqual(completedDestination, .apply)
        XCTAssertEqual(emptyDestination, .apply)
    }

    func testPermissionApplyDestinationFallsBackToApplyOnError() async {
        let response = HTTPURLResponse(
            url: URL(string: "https://api-test.zhifly.cn/mock")!,
            statusCode: 500,
            httpVersion: nil,
            headerFields: nil
        )!
        let session = MockURLSession(results: [.success((Data(), response))])
        let api = HomeAPI(client: APIClient(environment: .testing, session: session))
        let viewModel = HomeViewModel(appStore: appStore)

        let destination = await viewModel.resolvePermissionApplyDestination(api: api)
        XCTAssertEqual(destination, .apply)
    }

    func testRolePermissionBuildsCommonMenusAndStoreCardRole() async throws {
        appStore.session.roleCode = AppRoleCode.storeAdmin.rawValue
        let permissionResponse = RolePermissionResponse(
            role: RoleInfo(
                id: 46,
                name: "店铺管理员",
                roleCode: AppRoleCode.storeAdmin.rawValue,
                notes: nil,
                permission: [
                    PermissionItem(id: 1, pid: 0, name: "钱包", uri: "wallet", iconSrc: nil, children: nil),
                    PermissionItem(id: 2, pid: 0, name: "云盘", uri: "cloud_management", iconSrc: nil, children: nil),
                ]
            ),
            scenic: [ScenicInfo(id: 10, name: "测试景区", status: 1)]
        )
        let storeResponse = StoreListResponse(
            total: 1,
            list: [StoreItem(id: 5, scenicId: 10, name: "测试门店", address: "地址", status: 1, statusText: "营业中")]
        )

        let permissionJSON = try TestJSON.envelope(data: [permissionResponse])
        let storeJSON = try TestJSON.envelope(data: storeResponse)
        let session = MockURLSession(responses: [permissionJSON, storeJSON])
        let api = HomeAPI(client: APIClient(session: session))
        let viewModel = HomeViewModel(appStore: appStore)

        await viewModel.loadPermissions(api: api, force: true)

        XCTAssertFalse(viewModel.showPermissionDialog)
        XCTAssertTrue(viewModel.isMinimalTopRole)
        XCTAssertEqual(viewModel.commonMenus.count, 2)
        XCTAssertEqual(viewModel.storeItem?.name, "测试门店")
    }

    func testMarkNeedsPermissionReload() async throws {
        let permissionResponse = RolePermissionResponse(
            role: RoleInfo(
                id: 41,
                name: "摄影师",
                roleCode: AppRoleCode.photographer.rawValue,
                notes: nil,
                permission: [
                    PermissionItem(id: 1, pid: 0, name: "钱包", uri: "wallet", iconSrc: nil, children: nil),
                ]
            ),
            scenic: []
        )
        appStore.permissions.savePermissionItems([HomePermissionItem(id: "1", name: "钱包", uri: "wallet")])

        let permissionJSON = try TestJSON.envelope(data: [permissionResponse])
        let session = MockURLSession(responses: [permissionJSON])
        let api = HomeAPI(client: APIClient(session: session))
        let viewModel = HomeViewModel(appStore: appStore)

        viewModel.markNeedsPermissionReload()
        await viewModel.reloadIfNeeded(api: api)

        XCTAssertEqual(viewModel.commonMenus.map(\.uri), ["wallet"])
        XCTAssertEqual(session.requests.count, 1)
    }

    func testUnreadMessageStatusUsesCountEndpointAndUpdatesBadgeState() async {
        let api = HomeMessageCenterAPIMock()
        api.unreadCountResponses = [
            MessageUnreadCountResponse(unreadCount: 9),
            MessageUnreadCountResponse(unreadCount: 0),
        ]
        let viewModel = HomeViewModel(appStore: appStore)

        await viewModel.refreshUnreadMessageStatus(api: api)
        XCTAssertTrue(viewModel.hasUnreadMessages)
        XCTAssertEqual(viewModel.unreadMessageCount, 9)

        await viewModel.refreshUnreadMessageStatus(api: api)
        XCTAssertFalse(viewModel.hasUnreadMessages)
        XCTAssertEqual(viewModel.unreadMessageCount, 0)
        XCTAssertEqual(api.unreadCountCallCount, 2)
    }

    func testScenicHeaderMessageButtonShowsUnreadStateAndHandlesTap() throws {
        let headerView = HomeScenicHeaderView(frame: CGRect(x: 0, y: 0, width: 390, height: 100))
        var tapCount = 0
        headerView.onMessageTap = { tapCount += 1 }

        headerView.apply(scenicName: "测试景区", hasUnreadMessages: true)
        headerView.layoutIfNeeded()

        let button = try XCTUnwrap(
            findSubview(in: headerView) { $0.accessibilityIdentifier == "home_message_button" } as? UIButton
        )
        let unreadDot = try XCTUnwrap(
            findSubview(in: headerView) { $0.accessibilityIdentifier == "home_message_unread_dot" }
        )
        XCTAssertEqual(button.bounds.size, CGSize(width: 44, height: 44))
        XCTAssertEqual(button.accessibilityLabel, "消息中心，有未读消息")
        XCTAssertFalse(unreadDot.isHidden)

        button.sendActions(for: .touchUpInside)
        XCTAssertEqual(tapCount, 1)

        headerView.apply(scenicName: "测试景区", hasUnreadMessages: false)
        XCTAssertEqual(button.accessibilityLabel, "消息中心")
        XCTAssertTrue(unreadDot.isHidden)
    }

    func testRefreshLocalDisplayStateClearsWhenNameMissing() {
        appStore.session.currentScenicId = 10
        appStore.session.currentScenicName = ""
        let viewModel = HomeViewModel(appStore: appStore)

        viewModel.refreshLocalDisplayState()

        XCTAssertEqual(viewModel.currentScenicName, "")
        XCTAssertEqual(appStore.session.currentScenicId, 0)
        XCTAssertEqual(appStore.session.currentScenicName, "")
    }

    func testRefreshLocalDisplayStateClearsWhenIdMissing() {
        appStore.session.currentScenicId = 0
        appStore.session.currentScenicName = "残留名称"
        let viewModel = HomeViewModel(appStore: appStore)

        viewModel.refreshLocalDisplayState()

        XCTAssertEqual(viewModel.currentScenicName, "")
        XCTAssertEqual(appStore.session.currentScenicName, "")
    }

    private func statusActionContainer(in view: HomeQuickActionsView) throws -> UIView {
        let stackView = try XCTUnwrap(view.subviews.compactMap { $0 as? UIStackView }.first)
        XCTAssertEqual(stackView.arrangedSubviews.count, 3)
        return stackView.arrangedSubviews[2]
    }

    private func statusLabel(in view: UIView) -> UILabel? {
        if let label = view as? UILabel {
            return label
        }
        for subview in view.subviews {
            if let label = statusLabel(in: subview) {
                return label
            }
        }
        return nil
    }

    private func statusIcon(in view: UIView) -> UIImageView? {
        if let imageView = view as? UIImageView {
            return imageView
        }
        for subview in view.subviews {
            if let imageView = statusIcon(in: subview) {
                return imageView
            }
        }
        return nil
    }

    private func findSubview(in view: UIView, matching predicate: (UIView) -> Bool) -> UIView? {
        if predicate(view) {
            return view
        }
        for subview in view.subviews {
            if let match = findSubview(in: subview, matching: predicate) {
                return match
            }
        }
        return nil
    }
}

/// 首页未读消息查询替身。
@MainActor
private final class HomeMessageCenterAPIMock: MessageCenterServing {
    var unreadCountResponses: [MessageUnreadCountResponse] = []
    var unreadCountCallCount = 0

    func list(lastId: Int, limit: Int, unread: Int) async throws -> MessageListResponse {
        MessageListResponse()
    }

    func unreadCount() async throws -> MessageUnreadCountResponse {
        unreadCountCallCount += 1
        guard !unreadCountResponses.isEmpty else { return MessageUnreadCountResponse() }
        return unreadCountResponses.removeFirst()
    }

    func markAsRead(messageId: Int) async throws -> MessageUnreadCountResponse {
        MessageUnreadCountResponse()
    }

    func markAllAsRead() async throws -> MessageReadAllResponse {
        MessageReadAllResponse()
    }

    func delete(messageId: Int) async throws {}
}

private extension UIColor {

    var homeTestHexRGB: UInt {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0
        getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        let r = Int(round(red * 255))
        let g = Int(round(green * 255))
        let b = Int(round(blue * 255))
        return UInt(r << 16 | g << 8 | b)
    }
}
