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

import CoreImage
import CoreImage.CIFilterBuiltins
import Combine
import Foundation
import UIKit

@MainActor
/// 收款页 ViewModel，管理收款码加载、动态金额二维码、推送状态和到账轮询。
final class PaymentCollectionViewModel: ObservableObject {
    @Published var staticPayUrl = ""
    @Published var dynamicPayUrl = ""
    @Published var amountText = ""
    @Published var remarkText = ""
    @Published var currentPayUrl = ""
    @Published var qrImage: UIImage?
    @Published var errorMessage: String?
    @Published var status: PaymentCollectionStatus = .idle
    @Published var isVoiceBroadcast = false
    @Published var scanSuccessTime: String?
    @Published var paymentSuccessData: PaymentSuccessDisplayData?

    @Published private var knownRecordIDs = Set<String>()
    private var isLoading = false
    private var isPolling = false
    private let preferencesStore: AppPreferencesStore
    private let pushEventCenter: PaymentPushEventCenter
    private var pushEventsCancellable: AnyCancellable?
    private let context = CIContext()
    private let qrFilter = CIFilter.qrCodeGenerator()

    /// 创建收款页 ViewModel，并注入偏好存储与推送事件中心。
    init(
        preferencesStore: AppPreferencesStore = AppPreferencesStore(),
        pushEventCenter: PaymentPushEventCenter = .shared
    ) {
        self.preferencesStore = preferencesStore
        self.pushEventCenter = pushEventCenter
        isVoiceBroadcast = preferencesStore.loadPaymentReceiveVoiceEnabled()
        startListeningPushEvents()
    }

    /// 当前静态收款码是否可展示。
    var hasStaticPayCode: Bool {
        !staticPayUrl.paymentTrimmed.isEmpty
    }

    /// 金额是否满足设置动态二维码条件。
    var canConfirmAmount: Bool {
        guard let amount = Self.normalizedMoney(amountText) else { return false }
        return (Decimal(string: amount) ?? 0) > 0
    }

    /// 加载当前景区收款码，无景区时清空收款状态。
    func loadPayCode(api: PaymentServing, scenicId: Int?) async {
        guard let scenicId else {
            resetPayCode()
            errorMessage = "请先选择景区"
            return
        }

        isLoading = true
        errorMessage = nil
        defer { isLoading = false }

        do {
            let response = try await api.payCode(scenicId: scenicId)
            staticPayUrl = response.staticPayUrl
            dynamicPayUrl = response.dynamicPayUrl
            currentPayUrl = response.staticPayUrl
            qrImage = makeQRCode(from: currentPayUrl)
            status = .idle
            scanSuccessTime = nil
            paymentSuccessData = nil
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    /// 打开设置金额弹窗前清空输入并校验静态码是否可用。
    @discardableResult
    func prepareAmountDialog() -> Bool {
        guard hasStaticPayCode else {
            errorMessage = "请先获取收款码"
            return false
        }
        amountText = ""
        remarkText = ""
        return true
    }

    /// 确认设置金额并更新主二维码，对齐 Android confirmAmount。
    @discardableResult
    func confirmAmount() -> Bool {
        guard hasStaticPayCode else {
            errorMessage = "请先获取收款码"
            return false
        }
        guard let amount = Self.normalizedMoney(amountText), (Decimal(string: amount) ?? 0) > 0 else {
            errorMessage = "请输入有效收款金额"
            return false
        }
        guard amountText.matches(regex: #"^\d+(\.\d{1,2})?$"#) else {
            errorMessage = "金额最多支持两位小数"
            return false
        }
        guard applyDynamicAmount(normalizedAmount: amount) else {
            return false
        }
        scanSuccessTime = nil
        paymentSuccessData = nil
        return true
    }

    /// 切换收款到账语音提醒开关并持久化。
    func toggleVoiceBroadcast() {
        isVoiceBroadcast.toggle()
        preferencesStore.savePaymentReceiveVoiceEnabled(isVoiceBroadcast)
    }

    /// 处理收款相关推送事件，对齐 Android dealWithPushEvent。
    func handlePaymentPush(type: Int, dataJSON: String?) {
        if type == 6 {
            scanSuccessTime = Self.currentTimestamp()
            paymentSuccessData = nil
            return
        }

        guard type == 1,
              let dataJSON,
              let data = dataJSON.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
            return
        }

        let orderAmount = Self.stringValue(json["order_amount"])
        guard !orderAmount.isEmpty else { return }

        paymentSuccessData = PaymentSuccessDisplayData(
            orderAmount: orderAmount,
            orderNumber: Self.stringValue(json["order_number"]),
            payTime: Self.formattedPayTime(from: json["pay_time"]),
            remark: Self.stringValue(json["remark"])
        )
        scanSuccessTime = nil
        status = .idle
    }

    /// 根据输入金额和备注生成动态二维码 URL。
    @discardableResult
    func applyDynamicAmount() -> Bool {
        guard let amount = Self.normalizedMoney(amountText) else {
            errorMessage = "请输入有效收款金额"
            return false
        }
        return applyDynamicAmount(normalizedAmount: amount)
    }

    /// 记录当前已知收款记录，轮询时只把新增记录判定为本次到账。
    func primePaymentRecords(api: PaymentServing, scenicId: Int?) async {
        guard let scenicId else { return }
        if let response = try? await api.paymentCollectionRecords(scenicId: scenicId) {
            knownRecordIDs = Set(response.list.map(\.id))
        }
    }

    /// 轮询收款记录，命中新记录后进入成功态。
    func pollUntilPaymentDetected(
        api: PaymentServing,
        scenicId: Int?,
        maxAttempts: Int = 60,
        intervalNanoseconds: UInt64 = 2_000_000_000
    ) async {
        guard let scenicId else {
            status = .failed("请先选择景区")
            return
        }
        guard maxAttempts > 0 else { return }

        isPolling = true
        defer { isPolling = false }

        for attempt in 0 ..< maxAttempts {
            if Task.isCancelled { return }
            do {
                let response = try await api.paymentCollectionRecords(scenicId: scenicId)
                if let item = matchedNewRecord(in: response.list) {
                    knownRecordIDs.insert(item.id)
                    applySuccessState(from: item)
                    return
                }
            } catch {
                status = .failed(error.localizedDescription)
                return
            }
            if attempt < maxAttempts - 1 {
                try? await Task.sleep(nanoseconds: intervalNanoseconds)
            }
        }
        status = .failed("暂未查询到到账记录，请稍后刷新收款记录")
    }

    /// 清空收款码和轮询状态。
    func resetPayCode() {
        staticPayUrl = ""
        dynamicPayUrl = ""
        currentPayUrl = ""
        qrImage = nil
        knownRecordIDs = []
        status = .idle
        scanSuccessTime = nil
        paymentSuccessData = nil
    }

    /// 将输入金额规范成最多两位小数。
    static func normalizedMoney(_ rawValue: String) -> String? {
        let text = rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !text.isEmpty else { return nil }
        let sanitized = text.replacingOccurrences(of: ",", with: "")
        guard let decimal = Decimal(string: sanitized), decimal > 0 else { return nil }
        let number = NSDecimalNumber(decimal: decimal)
        let handler = NSDecimalNumberHandler(
            roundingMode: .plain,
            scale: 2,
            raiseOnExactness: false,
            raiseOnOverflow: false,
            raiseOnUnderflow: false,
            raiseOnDivideByZero: false
        )
        return number.rounding(accordingToBehavior: handler).stringValue
    }

    /// 生成当前时间字符串，格式对齐 Android 扫码成功展示。
    static func currentTimestamp() -> String {
        let formatter = DateFormatter()
        formatter.locale = Locale(identifier: "zh_CN")
        formatter.dateFormat = "yyyy-MM-dd HH:mm"
        return formatter.string(from: Date())
    }

    /// 从推送或接口字段中解析付款时间。
    static func formattedPayTime(from value: Any?) -> String {
        if let text = value as? String, !text.isEmpty {
            return text
        }
        if let number = value as? NSNumber {
            let timestamp = number.int64Value
            let epochSecond = timestamp > 1_000_000_000_000 ? timestamp / 1000 : timestamp
            let date = Date(timeIntervalSince1970: TimeInterval(epochSecond))
            let formatter = DateFormatter()
            formatter.locale = Locale(identifier: "zh_CN")
            formatter.dateFormat = "yyyy-MM-dd HH:mm"
            return formatter.string(from: date)
        }
        return ""
    }

    /// 从 JSON 字段中提取字符串值。
    static func stringValue(_ value: Any?) -> String {
        switch value {
        case let string as String:
            return string
        case let number as NSNumber:
            return number.stringValue
        case .some(let value):
            return "\(value)"
        case nil:
            return ""
        }
    }

    /// 订阅收款推送事件并转发到 ViewModel 状态。
    private func startListeningPushEvents() {
        pushEventsCancellable = pushEventCenter.events
            .sink { [weak self] event in
                self?.handlePaymentPush(type: event.type, dataJSON: event.dataJSON)
            }
    }

    /// 根据输入金额和备注生成动态二维码 URL。
    @discardableResult
    private func applyDynamicAmount(normalizedAmount amount: String) -> Bool {
        guard !dynamicPayUrl.paymentTrimmed.isEmpty else {
            errorMessage = "动态收款码暂不可用"
            return false
        }

        var components = URLComponents(string: dynamicPayUrl)
        var queryItems = components?.queryItems ?? []
        queryItems.removeAll { $0.name == "amount" || $0.name == "remark" }
        queryItems.append(URLQueryItem(name: "amount", value: amount))
        if !remarkText.paymentTrimmed.isEmpty {
            queryItems.append(URLQueryItem(name: "remark", value: remarkText.paymentTrimmed))
        }
        components?.queryItems = queryItems
        currentPayUrl = components?.url?.absoluteString ?? "\(dynamicPayUrl)&amount=\(amount)"
        qrImage = makeQRCode(from: currentPayUrl)
        amountText = amount
        status = .waiting
        return true
    }

    /// 轮询命中成功后同步 Android 付款详情卡所需字段。
    private func applySuccessState(from item: PaymentCollectionRecordItem) {
        status = .success(item)
        paymentSuccessData = PaymentSuccessDisplayData(
            orderAmount: item.orderAmount,
            orderNumber: item.orderNumber,
            payTime: [item.createDate, item.createTime]
                .filter { !$0.isEmpty }
                .joined(separator: " "),
            remark: remarkText.paymentTrimmed
        )
        scanSuccessTime = nil
    }

    /// 从收款记录中找到本次动态收款对应的新记录。
    private func matchedNewRecord(in records: [PaymentCollectionRecordItem]) -> PaymentCollectionRecordItem? {
        let newRecords = records.filter { !knownRecordIDs.contains($0.id) }
        guard !newRecords.isEmpty else { return nil }
        guard let amount = Self.normalizedMoney(amountText) else {
            return newRecords.first
        }
        return newRecords.first { Self.normalizedMoney($0.orderAmount) == amount } ?? newRecords.first
    }

    /// 生成二维码图片，保存相册和页面预览都使用同一张图。
    private func makeQRCode(from text: String) -> UIImage? {
        guard !text.paymentTrimmed.isEmpty,
              let data = text.data(using: .utf8) else {
            return nil
        }
        qrFilter.setValue(data, forKey: "inputMessage")
        qrFilter.setValue("M", forKey: "inputCorrectionLevel")
        guard let output = qrFilter.outputImage else { return nil }
        let transformed = output.transformed(by: CGAffineTransform(scaleX: 10, y: 10))
        guard let cgImage = context.createCGImage(transformed, from: transformed.extent) else { return nil }
        return UIImage(cgImage: cgImage)
    }
}

private extension String {
    /// 收款模块内部使用的去空白文本，避免依赖其他文件的 fileprivate 扩展。
    var paymentTrimmed: String {
        trimmingCharacters(in: .whitespacesAndNewlines)
    }

    /// 判断字符串是否匹配正则表达式。
    func matches(regex pattern: String) -> Bool {
        range(of: pattern, options: .regularExpression) != nil
    }
}

@MainActor
/// 收款记录 ViewModel，管理收款记录加载、日期分组和汇总兜底。
final class PaymentCollectionRecordViewModel: ObservableObject {
    @Published var groups: [PaymentCollectionRecordGroup] = []
    private var isLoading = false
    @Published var errorMessage: String?

    /// 加载当前景区收款记录，无景区时不请求接口。
    func load(api: PaymentServing, scenicId: Int?) async {
        guard let scenicId else {
            groups = []
            errorMessage = "请先选择景区"
            return
        }

        isLoading = true
        errorMessage = nil
        defer { isLoading = false }

        do {
            let response = try await api.paymentCollectionRecords(scenicId: scenicId)
            groups = Self.makeGroups(from: response)
        } catch {
            errorMessage = error.localizedDescription
        }
    }

    /// 将后端响应转换为可展示日期分组，缺少 analyse 时从 list 兜底生成汇总。
    static func makeGroups(from response: PaymentCollectionRecordResponse) -> [PaymentCollectionRecordGroup] {
        if !response.analyse.isEmpty {
            return response.analyse.map { analyse in
                PaymentCollectionRecordGroup(
                    analyse: analyse,
                    items: response.list.filter { $0.createDate == analyse.date }
                )
            }
        }

        let grouped = Dictionary(grouping: response.list, by: \.createDate)
        return grouped.keys.sorted(by: >).map { date in
            let items = grouped[date] ?? []
            let total = items.reduce(Decimal(0)) { partial, item in
                partial + (Decimal(string: item.orderAmount) ?? 0)
            }
            let analyse = PaymentCollectionRecordAnalyseItem(
                date: date,
                orderCount: items.count,
                orderAmountSum: NSDecimalNumber(decimal: total).stringValue
            )
            return PaymentCollectionRecordGroup(analyse: analyse, items: items)
        }
    }
}
