//
//  AccountContextLoader.swift
//  suixinkan
//
//  Created by Codex on 2026/6/22.
//

import Foundation

@MainActor
/// 用户资料服务协议，抽象用户信息读取能力以便测试替换。
protocol UserProfileServing {
    /// 获取当前登录账号的用户资料。
    func userInfo() async throws -> UserInfoResponse
}

@MainActor
/// 账号上下文加载器，统一装配用户资料、角色权限、景区和门店上下文。
struct AccountContextLoader {
    /// 刷新账号上下文，权限失败会向外抛错，景区和门店按旧工程规则兜底。
    func refresh(
        accountContext: AccountContext,
        permissionContext: PermissionContext,
        profileAPI: UserProfileServing,
        accountContextAPI: AccountContextServing,
        cachedCurrentRoleId: Int? = nil,
        cachedCurrentScenicId: Int? = nil,
        cachedCurrentStoreId: Int? = nil
    ) async throws {
        async let userData = profileAPI.userInfo()
        async let roleData = accountContextAPI.rolePermissions()
        let (userInfo, rolePermissions) = try await (userData, roleData)

        permissionContext.replaceRolePermissions(rolePermissions, currentRoleId: cachedCurrentRoleId)

        let scenicResponse = await loadScenicList(accountContextAPI: accountContextAPI, rolePermissions: rolePermissions)
        let storesResponse = await loadStores(accountContextAPI: accountContextAPI)
        let profile = makeProfile(from: userInfo, fallback: accountContext.profile)
        let scenicScopes = scopedScenics(
            permissionContext: permissionContext,
            scenicResponse: scenicResponse
        )
        let storeScopes = storesResponse?.list.map { store in
            BusinessScope(id: store.id, name: store.name, kind: .store, parentScenicId: store.scenicId)
        } ?? []

        accountContext.replaceProfile(profile)
        accountContext.replaceScopes(
            scenic: scenicScopes,
            stores: storeScopes,
            currentScenicId: cachedCurrentScenicId,
            currentStoreId: cachedCurrentStoreId
        )
    }

    /// 景区列表接口失败时，从角色权限中的景区数据去重构造兜底列表。
    private func loadScenicList(
        accountContextAPI: AccountContextServing,
        rolePermissions: [RolePermissionResponse]
    ) async -> ScenicListAllResponse {
        do {
            return try await accountContextAPI.scenicListAll()
        } catch {
            let fallback = uniqueScenics(from: rolePermissions)
            return ScenicListAllResponse(total: fallback.count, list: fallback)
        }
    }

    /// 门店列表接口失败时返回 nil，不阻断登录和恢复流程。
    private func loadStores(accountContextAPI: AccountContextServing) async -> ListPayload<StoreItem>? {
        try? await accountContextAPI.storeAll()
    }

    /// 根据当前角色优先返回角色可访问景区，角色无景区时使用景区列表接口结果。
    private func scopedScenics(
        permissionContext: PermissionContext,
        scenicResponse: ScenicListAllResponse
    ) -> [BusinessScope] {
        let roleScenics = permissionContext.currentRoleScenicScopes()
        if !roleScenics.isEmpty {
            return roleScenics
        }
        return scenicResponse.list.map {
            BusinessScope(id: $0.id, name: $0.name, kind: .scenic)
        }
    }

    /// 从角色权限中去重生成景区列表。
    private func uniqueScenics(from rolePermissions: [RolePermissionResponse]) -> [ScenicListItem] {
        var seen = Set<Int>()
        var result: [ScenicListItem] = []
        for permission in rolePermissions {
            for scenic in permission.scenic where seen.insert(scenic.id).inserted {
                result.append(ScenicListItem(id: scenic.id, name: scenic.name))
            }
        }
        return result
    }

    /// 将用户资料接口响应转换为全局账号资料。
    private func makeProfile(from userInfo: UserInfoResponse, fallback: AccountProfile?) -> AccountProfile {
        AccountProfile(
            userId: fallback?.userId ?? "",
            displayName: nonEmpty(userInfo.nickname) ?? nonEmpty(userInfo.realName) ?? fallback?.displayName ?? "未设置昵称",
            phone: nonEmpty(userInfo.phone) ?? fallback?.phone,
            avatarURL: nonEmpty(userInfo.avatar) ?? fallback?.avatarURL
        )
    }

    /// 去除空白字符后返回非空字符串。
    private func nonEmpty(_ value: String?) -> String? {
        let text = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        return text.isEmpty ? nil : text
    }
}
