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

import Foundation

/// 钱包汇总响应实体，表示累计收益、当前余额和可提现金额。
struct WalletSummaryResponse: Decodable, Equatable {
    let amountTotal: String
    let amountCurrentBalance: String
    let amountWithdrawable: String

    enum CodingKeys: String, CodingKey {
        case amountTotal = "amount_total"
        case amountCurrentBalance = "amount_current_balance"
        case amountWithdrawable = "amount_withdrawable"
    }

    /// 创建钱包汇总响应实体，主要用于测试和空状态。
    init(amountTotal: String = "0.00", amountCurrentBalance: String = "0.00", amountWithdrawable: String = "0.00") {
        self.amountTotal = amountTotal
        self.amountCurrentBalance = amountCurrentBalance
        self.amountWithdrawable = amountWithdrawable
    }

    /// 自定义解码，兼容金额字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        amountTotal = try container.decodeLossyString(forKey: .amountTotal)
        amountCurrentBalance = try container.decodeLossyString(forKey: .amountCurrentBalance)
        amountWithdrawable = try container.decodeLossyString(forKey: .amountWithdrawable)
    }
}

/// 钱包收益明细响应实体，表示日分组收益和分页总数。
struct WalletEarningDetailResponse: Decodable, Equatable {
    let totalAmount: String
    let totalPoints: Int
    let total: Int
    let list: [WalletEarningDetailGroup]

    enum CodingKeys: String, CodingKey {
        case totalAmount = "total_amount"
        case totalPoints = "total_points"
        case total
        case list
    }

    /// 创建收益明细响应实体，主要用于测试替身。
    init(totalAmount: String = "0.00", totalPoints: Int = 0, total: Int = 0, list: [WalletEarningDetailGroup] = []) {
        self.totalAmount = totalAmount
        self.totalPoints = totalPoints
        self.total = total
        self.list = list
    }

    /// 自定义解码，兼容后端字段缺失和类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        totalAmount = try container.decodeLossyString(forKey: .totalAmount)
        totalPoints = try container.decodeLossyInt(forKey: .totalPoints) ?? 0
        total = try container.decodeLossyInt(forKey: .total) ?? 0
        list = try container.decodeIfPresent([WalletEarningDetailGroup].self, forKey: .list) ?? []
    }
}

/// 钱包收益日分组实体，表示某一天的收益、积分和明细。
struct WalletEarningDetailGroup: Decodable, Equatable, Identifiable {
    let date: String
    let dayAmount: String
    let dayPoints: Int
    let items: [WalletEarningDetailItem]

    var id: String { date }

    enum CodingKeys: String, CodingKey {
        case date
        case dayAmount = "day_amount"
        case dayPoints = "day_points"
        case items
    }

    /// 创建收益日分组实体，主要用于测试替身。
    init(date: String, dayAmount: String = "0.00", dayPoints: Int = 0, items: [WalletEarningDetailItem] = []) {
        self.date = date
        self.dayAmount = dayAmount
        self.dayPoints = dayPoints
        self.items = items
    }

    /// 自定义解码，兼容后端字段缺失和类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        date = try container.decodeLossyString(forKey: .date)
        dayAmount = try container.decodeLossyString(forKey: .dayAmount)
        dayPoints = try container.decodeLossyInt(forKey: .dayPoints) ?? 0
        items = try container.decodeIfPresent([WalletEarningDetailItem].self, forKey: .items) ?? []
    }
}

/// 钱包收益明细实体，表示一笔订单收益或积分来源。
struct WalletEarningDetailItem: Decodable, Equatable, Identifiable {
    let id: Int64
    let amount: String
    let points: Int
    let type: Int
    let typeLabel: String
    let orderNumberSuffix: String
    let createdAt: String
    let withdrawLabel: String?
    let source: String?

    enum CodingKeys: String, CodingKey {
        case id
        case amount
        case points
        case type
        case typeLabel = "type_label"
        case orderNumberSuffix = "order_number_suffix"
        case createdAt = "created_at"
        case withdrawLabel = "withdraw_label"
        case source
    }

    /// 自定义解码，兼容字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = Int64(try container.decodeLossyInt(forKey: .id) ?? 0)
        amount = try container.decodeLossyString(forKey: .amount)
        points = try container.decodeLossyInt(forKey: .points) ?? 0
        type = try container.decodeLossyInt(forKey: .type) ?? 0
        typeLabel = try container.decodeLossyString(forKey: .typeLabel)
        orderNumberSuffix = try container.decodeLossyString(forKey: .orderNumberSuffix)
        createdAt = try container.decodeLossyString(forKey: .createdAt)
        withdrawLabel = try container.decodeIfPresent(String.self, forKey: .withdrawLabel)
        source = try container.decodeIfPresent(String.self, forKey: .source)
    }
}

/// 钱包提现记录响应实体，表示提现分页列表。
struct WalletWithdrawListResponse: Decodable, Equatable {
    let total: Int
    let item: [WalletWithdrawRecord]

    enum CodingKeys: String, CodingKey {
        case total
        case item
    }

    /// 创建提现记录响应实体，主要用于测试替身。
    init(total: Int = 0, item: [WalletWithdrawRecord] = []) {
        self.total = total
        self.item = item
    }

    /// 自定义解码，兼容 total 字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        total = try container.decodeLossyInt(forKey: .total) ?? 0
        item = try container.decodeIfPresent([WalletWithdrawRecord].self, forKey: .item) ?? []
    }
}

/// 钱包提现记录实体，表示一笔提现申请。
struct WalletWithdrawRecord: Decodable, Equatable, Identifiable {
    let id: Int64
    let amount: String
    let createdAt: String
    let statusLabel: String
    let expectedAt: String?
    let auditTime: String?
    let completedAt: String?

    enum CodingKeys: String, CodingKey {
        case id
        case amount
        case createdAt = "created_at"
        case statusLabel = "status_label"
        case expectedAt = "expected_at"
        case auditTime = "audit_time"
        case completedAt = "completed_at"
    }

    /// 自定义解码，兼容字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = Int64(try container.decodeLossyInt(forKey: .id) ?? 0)
        amount = try container.decodeLossyString(forKey: .amount)
        createdAt = try container.decodeLossyString(forKey: .createdAt)
        statusLabel = try container.decodeLossyString(forKey: .statusLabel)
        expectedAt = try container.decodeIfPresent(String.self, forKey: .expectedAt)
        auditTime = try container.decodeIfPresent(String.self, forKey: .auditTime)
        completedAt = try container.decodeIfPresent(String.self, forKey: .completedAt)
    }
}

/// 银行卡信息响应实体，包裹当前银行卡资料。
struct BankCardInfoResponse: Decodable, Equatable {
    let bankCard: WalletBankCardInfo?

    enum CodingKeys: String, CodingKey {
        case bankCard = "bank_card"
    }
}

/// 钱包银行卡实体，表示银行卡资料和审核状态。
struct WalletBankCardInfo: Decodable, Equatable, Identifiable {
    var id: String { cardNumber }
    let realName: String
    let bankName: String
    let branchName: String
    let cardNumber: String
    let frontUrl: String?
    let backUrl: String?
    let provinceCode: String?
    let cityCode: String?
    let rejectReason: String?
    let auditStatus: Int
    let auditStatusLabel: String

    enum CodingKeys: String, CodingKey {
        case realName = "real_name"
        case bankName = "bank_name"
        case branchName = "branch_name"
        case cardNumber = "card_number"
        case frontUrl = "front_url"
        case backUrl = "back_url"
        case provinceCode = "province_code"
        case cityCode = "city_code"
        case rejectReason = "reject_reason"
        case auditStatus = "audit_status"
        case auditStatusLabel = "audit_status_label"
    }

    /// 自定义解码，兼容审核状态字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        realName = try container.decodeLossyString(forKey: .realName)
        bankName = try container.decodeLossyString(forKey: .bankName)
        branchName = try container.decodeLossyString(forKey: .branchName)
        cardNumber = try container.decodeLossyString(forKey: .cardNumber)
        frontUrl = try container.decodeIfPresent(String.self, forKey: .frontUrl)
        backUrl = try container.decodeIfPresent(String.self, forKey: .backUrl)
        provinceCode = try container.decodeIfPresent(String.self, forKey: .provinceCode)
        cityCode = try container.decodeIfPresent(String.self, forKey: .cityCode)
        rejectReason = try container.decodeIfPresent(String.self, forKey: .rejectReason)
        auditStatus = try container.decodeLossyInt(forKey: .auditStatus) ?? 0
        auditStatusLabel = try container.decodeLossyString(forKey: .auditStatusLabel)
    }
}

/// 银行列表响应实体，表示可选择银行名称。
struct BankListResponse: Decodable, Equatable {
    let banks: [String]

    enum CodingKeys: String, CodingKey {
        case banks
    }

    /// 创建银行列表响应实体，主要用于测试替身。
    init(banks: [String] = []) {
        self.banks = banks
    }
}

/// 省市区节点实体，表示提现银行卡设置中的地区树。
struct AreaNode: Decodable, Equatable, Identifiable {
    let id: String
    let code: String
    let name: String
    let children: [AreaNode]

    enum CodingKeys: String, CodingKey {
        case id
        case code
        case name
        case children
    }

    /// 自定义解码，兼容 id/code 类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        code = try container.decodeLossyString(forKey: .code)
        let rawId = try container.decodeLossyString(forKey: .id).trimmingCharacters(in: .whitespacesAndNewlines)
        id = rawId.isEmpty ? code : rawId
        name = try container.decodeLossyString(forKey: .name)
        children = try container.decodeIfPresent([AreaNode].self, forKey: .children) ?? []
    }
}

/// 提现信息响应实体，表示提现上限、手机号和银行卡摘要。
struct WithdrawInfoResponse: Decodable, Equatable {
    let amountWithdrawable: String
    let minWithdrawAmount: String
    let maxSingleWithdrawAmount: String
    let maxDailyWithdrawAmount: String
    let userPhone: String
    let bankCard: WithdrawBankCardInfo
    let withdrawInfo: [String]

    enum CodingKeys: String, CodingKey {
        case amountWithdrawable = "amount_withdrawable"
        case minWithdrawAmount = "min_withdraw_amount"
        case maxSingleWithdrawAmount = "max_single_withdraw_amount"
        case maxDailyWithdrawAmount = "max_daily_withdraw_amount"
        case userPhone = "user_phone"
        case bankCard = "bank_card"
        case withdrawInfo = "withdraw_info"
    }

    /// 自定义解码，兼容金额字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        amountWithdrawable = try container.decodeLossyString(forKey: .amountWithdrawable)
        minWithdrawAmount = try container.decodeLossyString(forKey: .minWithdrawAmount)
        maxSingleWithdrawAmount = try container.decodeLossyString(forKey: .maxSingleWithdrawAmount)
        maxDailyWithdrawAmount = try container.decodeLossyString(forKey: .maxDailyWithdrawAmount)
        userPhone = try container.decodeLossyString(forKey: .userPhone)
        bankCard = try container.decodeIfPresent(WithdrawBankCardInfo.self, forKey: .bankCard) ?? WithdrawBankCardInfo()
        withdrawInfo = try container.decodeIfPresent([String].self, forKey: .withdrawInfo) ?? []
    }
}

/// 提现银行卡摘要实体，表示提现页面展示的银行卡信息。
struct WithdrawBankCardInfo: Decodable, Equatable {
    let realName: String
    let bankName: String
    let cardNumber: String

    enum CodingKeys: String, CodingKey {
        case realName = "real_name"
        case bankName = "bank_name"
        case cardNumber = "card_number"
    }

    /// 创建提现银行卡摘要，主要用于空状态兜底。
    init(realName: String = "", bankName: String = "", cardNumber: String = "") {
        self.realName = realName
        self.bankName = bankName
        self.cardNumber = cardNumber
    }

    /// 自定义解码，兼容字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        realName = try container.decodeLossyString(forKey: .realName)
        bankName = try container.decodeLossyString(forKey: .bankName)
        cardNumber = try container.decodeLossyString(forKey: .cardNumber)
    }
}

/// 提现申请请求实体，表示金额和短信验证码。
struct WithdrawApplyRequest: Encodable {
    let amount: String
    let smsCode: String

    enum CodingKeys: String, CodingKey {
        case amount
        case smsCode = "sms_code"
    }
}

/// 银行卡更新请求实体，表示银行卡资料、证件图片 URL 和验证码。
struct UpdateBankInfoRequest: Encodable, Equatable {
    let realName: String
    let cardNumber: String
    let bankName: String
    let branchName: String
    let frontUrl: String
    let backUrl: String
    let provinceCode: String
    let cityCode: String
    let smsVerifyCode: String

    enum CodingKeys: String, CodingKey {
        case realName = "real_name"
        case cardNumber = "card_number"
        case bankName = "bank_name"
        case branchName = "branch_name"
        case frontUrl = "front_url"
        case backUrl = "back_url"
        case provinceCode = "province_code"
        case cityCode = "city_code"
        case smsVerifyCode = "sms_verify_code"
    }
}

/// 积分概览响应实体，表示当前积分可兑换状态。
struct PointOverviewResponse: Decodable, Equatable {
    let totalPoints: Int
    let availablePoints: Int
    let withdrawnPoints: Int
    let pendingPoints: Int
    let time: String

    enum CodingKeys: String, CodingKey {
        case totalPoints = "total_points"
        case availablePoints = "available_points"
        case withdrawnPoints = "withdrawn_points"
        case pendingPoints = "pending_points"
        case time
    }

    /// 创建积分概览响应实体，主要用于空状态和测试替身。
    init(totalPoints: Int = 0, availablePoints: Int = 0, withdrawnPoints: Int = 0, pendingPoints: Int = 0, time: String = "") {
        self.totalPoints = totalPoints
        self.availablePoints = availablePoints
        self.withdrawnPoints = withdrawnPoints
        self.pendingPoints = pendingPoints
        self.time = time
    }

    /// 自定义解码，兼容积分字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        totalPoints = try container.decodeLossyInt(forKey: .totalPoints) ?? 0
        availablePoints = try container.decodeLossyInt(forKey: .availablePoints) ?? 0
        withdrawnPoints = try container.decodeLossyInt(forKey: .withdrawnPoints) ?? 0
        pendingPoints = try container.decodeLossyInt(forKey: .pendingPoints) ?? 0
        time = try container.decodeLossyString(forKey: .time)
    }
}

/// 积分兑换申请请求实体。
struct PointWithdrawApplyRequest: Encodable {
    let points: Int
    let remark: String
}

/// 积分兑换列表请求实体。
struct PointWithdrawListRequest: Encodable {
    let status: Int?
    let page: Int
    let pageSize: Int

    enum CodingKeys: String, CodingKey {
        case status
        case page
        case pageSize = "page_size"
    }
}

/// 积分兑换列表响应实体。
struct PointWithdrawListResponse: Decodable, Equatable {
    let total: Int
    let list: [PointWithdrawItem]

    enum CodingKeys: String, CodingKey {
        case total
        case list
    }

    /// 创建积分兑换列表响应实体，主要用于测试替身。
    init(total: Int = 0, list: [PointWithdrawItem] = []) {
        self.total = total
        self.list = list
    }

    /// 自定义解码，兼容字段缺失和类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        total = try container.decodeLossyInt(forKey: .total) ?? 0
        list = try container.decodeIfPresent([PointWithdrawItem].self, forKey: .list) ?? []
    }
}

/// 积分兑换记录实体。
struct PointWithdrawItem: Decodable, Equatable, Identifiable {
    let id: Int
    let points: Int
    let amount: Double
    let status: Int
    let createdAt: String

    enum CodingKeys: String, CodingKey {
        case id
        case points
        case amount
        case status
        case createdAt = "created_at"
    }

    /// 自定义解码，兼容字段类型不稳定。
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decodeLossyInt(forKey: .id) ?? 0
        points = try container.decodeLossyInt(forKey: .points) ?? 0
        amount = try container.decodeLossyDouble(forKey: .amount) ?? 0
        status = try container.decodeLossyInt(forKey: .status) ?? 0
        createdAt = try container.decodeLossyString(forKey: .createdAt)
    }
}

/// 钱包本地路由实体，表示钱包内二级页面。
enum WalletRoute: Hashable, Identifiable {
    case withdrawApply
    case bankCardSettings
    case pointsRedemption
    case realNameAuth

    var id: String {
        switch self {
        case .withdrawApply: "withdrawApply"
        case .bankCardSettings: "bankCardSettings"
        case .pointsRedemption: "pointsRedemption"
        case .realNameAuth: "realNameAuth"
        }
    }
}

/// 提现资格决策实体，表示点击提现后的下一步页面或提示。
enum WalletWithdrawDecision: Equatable {
    case route(WalletRoute)
    case message(String)
}

/// 钱包筛选区间实体，表示收益明细查询时间范围。
enum WalletDateFilter: String, CaseIterable, Identifiable {
    case last7
    case last30
    case last180

    var id: String { rawValue }

    /// 当前筛选区间的展示标题。
    var title: String {
        switch self {
        case .last7: "近7日"
        case .last30: "近30日"
        case .last180: "近180日"
        }
    }

    /// 根据当前日期计算开始和结束日期。
    func range(today: Date = Date(), calendar: Calendar = .current) -> (start: String, end: String) {
        let formatter = DateFormatter()
        formatter.calendar = calendar
        formatter.dateFormat = "yyyy-MM-dd"
        let days: Int
        switch self {
        case .last7:
            days = 6
        case .last30:
            days = 29
        case .last180:
            days = 179
        }
        let startDate = calendar.date(byAdding: .day, value: -days, to: today) ?? today
        return (formatter.string(from: startDate), formatter.string(from: today))
    }
}

private extension KeyedDecodingContainer {
    /// 将字符串、数字或空值宽松解码为字符串。
    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 value.truncatingRemainder(dividingBy: 1) == 0 ? String(Int(value)) : String(value)
        }
        return ""
    }

    /// 将字符串或数字宽松解码为整数。
    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
    }

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