//
//  AuthModels.swift
//  suixinkan
//
//  Created by Codex on 2026/6/18.
//

import Foundation

/// 登录请求实体，表示手机号密码登录接口需要的参数。
struct LoginRequest: Encodable {
    let username: String
    let type: Int
    let password: String
    let mobile: String
    let code: String

    /// 创建手机号密码登录请求，并填充后端要求的默认字段。
    init(username: String, password: String) {
        self.username = username
        self.type = 1
        self.password = password
        self.mobile = ""
        self.code = ""
    }
}

/// 账号选择请求实体，表示 set-user 接口需要的景区账号或门店账号 ID。
struct SetUserRequest: Encodable, Equatable {
    let storeUserId: Int?
    let ssUserId: Int?

    /// set-user 请求的 JSON 字段映射。
    enum CodingKeys: String, CodingKey {
        case storeUserId = "store_user_id"
        case ssUserId = "ss_user_id"
    }

    /// 创建账号选择请求，门店账号传 storeUserId，景区账号传 ssUserId。
    init(storeUserId: Int? = nil, ssUserId: Int? = nil) {
        self.storeUserId = storeUserId
        self.ssUserId = ssUserId
    }
}

/// 可切换账号实体，统一表示登录返回的景区账号和门店账号。
struct AccountSwitchAccount: Identifiable, Hashable {
    let accountType: String
    let businessUserId: Int
    let title: String
    let subtitle: String
    let phone: String
    let avatar: String
    let scenicName: String
    let storeId: Int?
    let storeName: String
    let scenicId: Int?
    let isCurrent: Bool

    var id: String {
        "\(accountType)_\(businessUserId)"
    }

    var isStoreUser: Bool {
        accountType == V9StoreUser.accountTypeValue
    }

    var accountTypeLabel: String {
        isStoreUser ? "门店账号" : "景区账号"
    }

    /// 转换为 set-user 接口请求参数。
    func toSetUserRequest() -> SetUserRequest {
        if isStoreUser {
            return SetUserRequest(storeUserId: businessUserId)
        }
        return SetUserRequest(ssUserId: businessUserId)
    }
}

/// 登录账号选择载荷，保存临时 token 和待用户选择的账号列表。
struct AccountSelectionPayload: Equatable, Identifiable {
    let id = UUID()
    let tempToken: String
    let accounts: [AccountSwitchAccount]

    var hasTempToken: Bool {
        !tempToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    }
}

/// v9 登录响应实体，包含 token、景区账号列表和门店账号列表。
struct V9AuthResponse: Decodable, Equatable {
    let token: String
    let scenicUsers: [V9ScenicUser]
    let storeUsers: [V9StoreUser]

    /// 合并景区账号和门店账号，供账号选择页展示。
    var accounts: [AccountSwitchAccount] {
        scenicUsers.map { $0.toAccountSwitchAccount() } + storeUsers.map { $0.toAccountSwitchAccount() }
    }

    /// 提取当前或首个账号作为全局账号资料。
    var primaryProfile: AccountProfile? {
        if let storeUser = storeUsers.first(where: \.isCurrent) ?? storeUsers.first {
            return AccountProfile(
                userId: String(storeUser.userId),
                displayName: storeUser.displayName,
                phone: storeUser.phone.nonEmpty,
                avatarURL: storeUser.avatar.nonEmpty
            )
        }

        if let scenicUser = scenicUsers.first(where: \.isCurrent) ?? scenicUsers.first {
            return AccountProfile(
                userId: String(scenicUser.userId),
                displayName: scenicUser.displayName,
                phone: scenicUser.phone.nonEmpty,
                avatarURL: nil
            )
        }

        return nil
    }

    /// 提取登录响应中的景区作用域，并按当前账号优先排序。
    var scenicScopes: [BusinessScope] {
        var seen = Set<Int>()
        let scenicFromScenicUsers = scenicUsers
            .sorted { $0.isCurrent && !$1.isCurrent }
            .compactMap { user -> BusinessScope? in
                guard user.scenicId > 0, seen.insert(user.scenicId).inserted else { return nil }
                return BusinessScope(id: user.scenicId, name: user.scenicName, kind: .scenic)
            }
        let scenicFromStoreUsers = storeUsers
            .sorted { $0.isCurrent && !$1.isCurrent }
            .compactMap { user -> BusinessScope? in
                guard user.scenicId > 0, seen.insert(user.scenicId).inserted else { return nil }
                return BusinessScope(id: user.scenicId, name: user.scenicName, kind: .scenic)
            }
        return scenicFromScenicUsers + scenicFromStoreUsers
    }

    /// 提取登录响应中的门店作用域，并按当前账号优先排序。
    var storeScopes: [BusinessScope] {
        var seen = Set<Int>()
        return storeUsers
            .sorted { $0.isCurrent && !$1.isCurrent }
            .compactMap { user -> BusinessScope? in
                guard user.storeId > 0, seen.insert(user.storeId).inserted else { return nil }
                return BusinessScope(
                    id: user.storeId,
                    name: user.storeName,
                    kind: .store,
                    parentScenicId: user.scenicId > 0 ? user.scenicId : nil
                )
            }
    }

    /// v9 登录响应的 JSON 字段映射。
    enum CodingKeys: String, CodingKey {
        case token
        case scenicUsers = "scenic_users"
        case storeUsers = "store_users"
    }

    /// 创建空响应或测试响应。
    init(token: String = "", scenicUsers: [V9ScenicUser] = [], storeUsers: [V9StoreUser] = []) {
        self.token = token
        self.scenicUsers = scenicUsers
        self.storeUsers = storeUsers
    }

    /// 自定义解码，兼容后端账号数组缺失的情况。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        token = try container.decodeLossyString(forKey: .token)
        scenicUsers = (try? container.decodeIfPresent([V9ScenicUser].self, forKey: .scenicUsers)) ?? []
        storeUsers = (try? container.decodeIfPresent([V9StoreUser].self, forKey: .storeUsers)) ?? []
    }
}

/// v9 景区账号实体，表示用户可进入的某个景区身份。
struct V9ScenicUser: Decodable, Equatable {
    static let accountTypeValue = "scenic_user"

    let accountType: String
    let id: Int
    let userId: Int
    let scenicUserId: Int
    let ssUserId: Int
    let username: String
    let realName: String
    let nickname: String
    let phone: String
    let scenicId: Int
    let scenicName: String
    let isCurrent: Bool

    /// set-user 使用的业务账号 ID，按后端可用字段优先级兜底。
    var businessUserId: Int {
        [ssUserId, scenicUserId, userId, id].first { $0 > 0 } ?? 0
    }

    /// 用于界面展示的景区账号名称。
    var displayName: String {
        scenicName.nonEmpty ?? nickname.nonEmpty ?? realName.nonEmpty ?? username
    }

    /// 转换为账号选择页使用的统一账号实体。
    func toAccountSwitchAccount() -> AccountSwitchAccount {
        AccountSwitchAccount(
            accountType: accountType.nonEmpty ?? Self.accountTypeValue,
            businessUserId: businessUserId,
            title: scenicName.nonEmpty ?? nickname.nonEmpty ?? realName.nonEmpty ?? "景区账号",
            subtitle: [realName.nonEmpty, nickname.nonEmpty]
                .compactMap(\.self)
                .joined(separator: " · "),
            phone: phone,
            avatar: "",
            scenicName: scenicName,
            storeId: nil,
            storeName: "",
            scenicId: scenicId > 0 ? scenicId : nil,
            isCurrent: isCurrent
        )
    }

    /// 景区账号响应的 JSON 字段映射。
    enum CodingKeys: String, CodingKey {
        case accountType = "account_type"
        case id
        case userId = "user_id"
        case scenicUserId = "scenic_user_id"
        case ssUserId = "ss_user_id"
        case username
        case realName = "real_name"
        case nickname
        case phone
        case scenicId = "scenic_id"
        case scenicName = "scenic_name"
        case isCurrent = "is_current"
    }

    /// 自定义解码，兼容 ID 字段类型不稳定和字段缺失。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        accountType = try container.decodeLossyString(forKey: .accountType).nonEmpty ?? Self.accountTypeValue
        id = try container.decodeLossyInt(forKey: .id) ?? 0
        userId = try container.decodeLossyInt(forKey: .userId) ?? 0
        scenicUserId = try container.decodeLossyInt(forKey: .scenicUserId) ?? 0
        ssUserId = try container.decodeLossyInt(forKey: .ssUserId) ?? 0
        username = try container.decodeLossyString(forKey: .username)
        realName = try container.decodeLossyString(forKey: .realName)
        nickname = try container.decodeLossyString(forKey: .nickname)
        phone = try container.decodeLossyString(forKey: .phone)
        scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0
        scenicName = try container.decodeLossyString(forKey: .scenicName)
        isCurrent = try container.decodeLossyBool(forKey: .isCurrent) ?? false
    }
}

/// v9 门店账号实体，表示用户可进入的某个门店身份。
struct V9StoreUser: Decodable, Equatable {
    static let accountTypeValue = "store_user"

    let accountType: String
    let id: Int
    let userId: Int
    let storeUserId: Int
    let username: String
    let userName: String
    let realName: String
    let phone: String
    let avatar: String
    let scenicId: Int
    let scenicName: String
    let storeId: Int
    let storeName: String
    let isCurrent: Bool

    /// set-user 使用的业务账号 ID，按后端可用字段优先级兜底。
    var businessUserId: Int {
        [storeUserId, userId, id].first { $0 > 0 } ?? 0
    }

    /// 用于界面展示的门店账号名称。
    var displayName: String {
        storeName.nonEmpty ?? scenicName.nonEmpty ?? realName.nonEmpty ?? userName.nonEmpty ?? username
    }

    /// 转换为账号选择页使用的统一账号实体。
    func toAccountSwitchAccount() -> AccountSwitchAccount {
        AccountSwitchAccount(
            accountType: accountType.nonEmpty ?? Self.accountTypeValue,
            businessUserId: businessUserId,
            title: storeName.nonEmpty ?? scenicName.nonEmpty ?? realName.nonEmpty ?? userName.nonEmpty ?? "门店账号",
            subtitle: [scenicName.nonEmpty, realName.nonEmpty ?? userName.nonEmpty]
                .compactMap(\.self)
                .joined(separator: " · "),
            phone: phone,
            avatar: avatar,
            scenicName: scenicName,
            storeId: storeId > 0 ? storeId : nil,
            storeName: storeName,
            scenicId: scenicId > 0 ? scenicId : nil,
            isCurrent: isCurrent
        )
    }

    /// 门店账号响应的 JSON 字段映射。
    enum CodingKeys: String, CodingKey {
        case accountType = "account_type"
        case id
        case userId = "user_id"
        case storeUserId = "store_user_id"
        case username
        case userName = "user_name"
        case realName = "real_name"
        case phone
        case avatar
        case scenicId = "scenic_id"
        case scenicName = "scenic_name"
        case storeId = "store_id"
        case storeName = "store_name"
        case isCurrent = "is_current"
    }

    /// 自定义解码，兼容 ID 字段类型不稳定和字段缺失。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        accountType = try container.decodeLossyString(forKey: .accountType).nonEmpty ?? Self.accountTypeValue
        id = try container.decodeLossyInt(forKey: .id) ?? 0
        userId = try container.decodeLossyInt(forKey: .userId) ?? 0
        storeUserId = try container.decodeLossyInt(forKey: .storeUserId) ?? 0
        username = try container.decodeLossyString(forKey: .username)
        userName = try container.decodeLossyString(forKey: .userName)
        realName = try container.decodeLossyString(forKey: .realName)
        phone = try container.decodeLossyString(forKey: .phone)
        avatar = try container.decodeLossyString(forKey: .avatar)
        scenicId = try container.decodeLossyInt(forKey: .scenicId) ?? 0
        scenicName = try container.decodeLossyString(forKey: .scenicName)
        storeId = try container.decodeLossyInt(forKey: .storeId) ?? 0
        storeName = try container.decodeLossyString(forKey: .storeName)
        isCurrent = try container.decodeLossyBool(forKey: .isCurrent) ?? false
    }
}

private extension KeyedDecodingContainer {
    /// 将 String、数字和 Bool 宽松解码为字符串。
    func decodeLossyString(forKey key: Key) throws -> String {
        if let value = try? decodeIfPresent(String.self, forKey: key) {
            return value
        }
        if let value = try? decodeIfPresent(Int.self, forKey: key) {
            return String(value)
        }
        if let value = try? decodeIfPresent(Double.self, forKey: key) {
            return String(value)
        }
        if let value = try? decodeIfPresent(Bool.self, forKey: key) {
            return value ? "true" : "false"
        }
        return ""
    }

    /// 将 Int、Double 或数字字符串宽松解码为整数。
    func decodeLossyInt(forKey key: Key) throws -> Int? {
        if let value = try? decodeIfPresent(Int.self, forKey: key) {
            return value
        }
        if let value = try? decodeIfPresent(String.self, forKey: key) {
            let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
            if let intValue = Int(text) {
                return intValue
            }
            if let doubleValue = Double(text) {
                return Int(doubleValue)
            }
        }
        if let value = try? decodeIfPresent(Double.self, forKey: key) {
            return Int(value)
        }
        return nil
    }

    /// 将 Bool、数字或布尔字符串宽松解码为布尔值。
    func decodeLossyBool(forKey key: Key) throws -> Bool? {
        if let value = try? decodeIfPresent(Bool.self, forKey: key) {
            return value
        }
        if let value = try? decodeIfPresent(Int.self, forKey: key) {
            return value != 0
        }
        if let value = try? decodeIfPresent(String.self, forKey: key) {
            let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
            if ["1", "true", "yes"].contains(normalized) { return true }
            if ["0", "false", "no"].contains(normalized) { return false }
        }
        return nil
    }
}

private extension String {
    var nonEmpty: String? {
        let text = trimmingCharacters(in: .whitespacesAndNewlines)
        return text.isEmpty ? nil : text
    }
}
