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

import CoreImage
import CoreImage.CIFilterBuiltins
import Foundation
import Observation
import UIKit

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

    private var knownRecordIDs = Set<String>()
    private let context = CIContext()
    private let qrFilter = CIFilter.qrCodeGenerator()

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

    /// 加载当前景区收款码，无景区时清空收款状态。
    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
        } catch {
            errorMessage = error.localizedDescription
        }
    }

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

    /// 记录当前已知收款记录，轮询时只把新增记录判定为本次到账。
    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)
                    status = .success(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
    }

    /// 将输入金额规范成最多两位小数。
    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
    }

    /// 从收款记录中找到本次动态收款对应的新记录。
    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)
    }
}

@MainActor
@Observable
/// 收款记录 ViewModel，管理收款记录加载、日期分组和汇总兜底。
final class PaymentCollectionRecordViewModel {
    var groups: [PaymentCollectionRecordGroup] = []
    var isLoading = false
    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)
        }
    }
}
