import Foundation // Mirrors Kotlin MibFinancingClient exactly. // Fetches the /financing?dashurl=1 HTML page and parses finance-card-holder data attributes. actor MibFinancingClient { private let wvBase = "https://faisamobilex-wv.mib.com.mv" private let session: MibSession private let urlSession: URLSession = { let cfg = URLSessionConfiguration.ephemeral cfg.timeoutIntervalForRequest = 30 cfg.timeoutIntervalForResource = 60 return URLSession(configuration: cfg) }() init(session: MibSession) { self.session = session } func fetchFinancing() async throws -> [MibFinanceDeal] { var req = URLRequest(url: URL(string: "\(wvBase)/financing?dashurl=1")!) req.setValue(cookieHeader, forHTTPHeaderField: "Cookie") req.setValue( "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", forHTTPHeaderField: "User-Agent" ) req.setValue("mv.com.mib.faisamobilex", forHTTPHeaderField: "X-Requested-With") let (data, response) = try await urlSession.data(for: req) let code = (response as? HTTPURLResponse)?.statusCode ?? 0 if code == 419 { throw MibError.sessionExpired } if code >= 500 { throw MibError.networkError("HTTP \(code)") } guard let html = String(data: data, encoding: .utf8) else { return [] } return parseHtml(html) } // MARK: - HTML parsing (mirrors Kotlin regex approach) private func parseHtml(_ html: String) -> [MibFinanceDeal] { guard let cardRe = try? NSRegularExpression(pattern: #"finance-card-holder[^>]+>"#), let attrRe = try? NSRegularExpression(pattern: #"data-(\w+)\s*=\s*"([^"]*)""#) else { return [] } let ns = html as NSString return cardRe.matches(in: html, range: NSRange(location: 0, length: ns.length)).compactMap { m in let cardStr = ns.substring(with: m.range) let cardNS = cardStr as NSString var attrs: [String: String] = [:] attrRe.matches(in: cardStr, range: NSRange(location: 0, length: cardNS.length)).forEach { a in let key = cardNS.substring(with: a.range(at: 1)) let val = cardNS.substring(with: a.range(at: 2)) attrs[key] = val } guard let dealNo = attrs["dealNo"] else { return nil } return MibFinanceDeal( dealNo: dealNo, productDesc: attrs["productDesc"] ?? "", dealStatus: attrs["dealStatus"] ?? "", statusDesc: attrs["statusDesc"] ?? "", dealAmount: Double(attrs["dealAmount"] ?? "") ?? 0, paidAmount: Double(attrs["paidAmount"] ?? "") ?? 0, outstandingAmount: Double(attrs["outstandingAmount"] ?? "") ?? 0, dealDate: attrs["dealDate"] ?? "", overdueAmount: Double(attrs["overdueAmount"] ?? "") ?? 0, installmentAmount: Double(attrs["installmentAmount"] ?? "") ?? 0, noOfInstallments: Int(attrs["noOfInstallments"] ?? "") ?? 0, lastPaidDate: attrs["lastPaidDate"] ?? "", lastPayAmount: Double(attrs["lastPayAmount"] ?? "") ?? 0, currency: attrs["curCodeDesc"] ?? "MVR" ) } } private var cookieHeader: String { "mbmodel=IOS-1.0; xxid=\(session.xxid); IBSID=\(session.xxid); mbnonce=\(session.nonceGenerator); time-tracker=597" } }