//
//  MandiriEmoneyApi.swift
//  Emoney Info
//
//  Created by Wira Irawan on 27/07/24.
//

import Foundation
import CoreNFC

public class MandiriEmoneyApi : UnifiedNfcApi {
    var emoney : Emoney = Emoney()
    var riwayatList: [RiwayatCard] = []
    var cardType : Int?
    var mapv1 : String = ""
    var start = 0
    var finish = 256
    var finish2 = 10
    
    public override init() {}
    
    public func getCardNumber(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MANDIRI_APDU01, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.emoney.setCardLabel("Mandiri e-Money")
                self.emoney.setCardNumber(response.getData().hexEncodedString().subString(from: 0, to: 16))
                self.cardType = response.getData().hexEncodedString().subString(from: 36, to: 38).hex2decimal()
                debugLog(self.cardType!)
                self.getBalance()
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func getBalance(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.MANDIRI_APDU02, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                debugLog(response.getData().hexEncodedString())
                let balance = response.getData().hexEncodedString().subString(from: 0, to: 8)
                self.emoney.setBalance(self.getRealBalance(reverseHexa: balance))
                self.resetHistoryState()
//                self.updateScreen()
                if (self.cardType! == 131){
                    self.getLogStep01(index: self.start)
                } else {
                    self.getLogStep02(index: self.start)
                }
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func getLogStep01(index : Int){
        //00 d1 00 00 00
        let hex = String(index, radix: 16)
        let MANDIRI_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xD1, p1Parameter : hex.hex2byte().bytes.first!, p2Parameter : 0x00, data : Data(), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
        apduRunner.exchangeApdu(apduCommand: MANDIRI_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.mapv1.append(response.getData().hexEncodedString())
                self.start+=1
                if (self.start < (self.finish)){
                    self.getLogStep01(index: self.start)
                } else {
                    self.parseNewLog()
                    self.start = 0
                    self.finalizeHistory()
                    self.emoney.setRiwayatList(self.riwayatList)
                    self.emoney.setTampilRiwayat(true)
                    self.updateScreen()
                    self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                    self.apduRunner.invalidateSession()
                }
            } else {
                self.parseNewLog()
                self.start = 0
                self.finalizeHistory()
                self.emoney.setRiwayatList(self.riwayatList)
                self.emoney.setTampilRiwayat(true)
                self.updateScreen()
                self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                self.apduRunner.invalidateSession()
            }
        })
    }
    
    func parseNewLog(){
        let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
        if (logs.count % 48 != 0){
            return
        }
        let total = logs.count/48
        for i in 0..<total {
            let start = i*48
            let end = start + 48
            let data = logs.subString(from: start, to: end)
            let riwayat = RiwayatCard()
            let time = self.getTransactionTime(formatDate: data.subString(from: 0, to: 6), formatTime: data.subString(from: 6, to: 12))
            riwayat.setTransactionTime(time!)
            let processType = Int(data.subString(from: 28, to: 32))
            if (processType == 100){
                riwayat.setProsesTipe(0)
                riwayat.setTitle("Top Up")
            } else {
                riwayat.setProsesTipe(1)
                riwayat.setTitle("Payment")
            }
            riwayat.setLocationId(data.subString(from: 12, to: 20))
            let amount = data.subString(from: 32, to: 40)
            riwayat.setAmount(getRealBalance(reverseHexa: amount))
            riwayatList.append(riwayat)
        }
    }
    
    private func getLogStep02(index : Int){
        //00 b2 00 00 1e
        let hex = String(index, radix: 16)
        let MANDIRI_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB2, p1Parameter : hex.hex2byte().bytes.first!, p2Parameter : 0x00, data : Data(), expectedResponseLength : 30)
        apduRunner.exchangeApdu(apduCommand: MANDIRI_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.mapv1.append(response.getData().hexEncodedString())
                self.start+=1
                if (self.start < (self.finish2)){
                    self.getLogStep02(index: self.start)
                } else {
                    self.parseOldLog()
                    self.start = 0
                    self.finalizeHistory()
                    self.emoney.setRiwayatList(self.riwayatList)
                    self.emoney.setTampilRiwayat(true)
                    self.updateScreen()
                    self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                    self.apduRunner.invalidateSession()
                }
            } else {
                self.parseOldLog()
                self.start = 0
                self.finalizeHistory()
                self.emoney.setRiwayatList(self.riwayatList)
                self.emoney.setTampilRiwayat(true)
                self.updateScreen()
                self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                self.apduRunner.invalidateSession()
            }
        })
    }
    
    func parseOldLog(){
        let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
        if (logs.count % 60 != 0){
            return
        }
        let total = logs.count/60
        for i in 0..<total {
            let start = i*60
            let end = start + 60
            let data = logs.subString(from: start, to: end)
            let riwayat = riwayatCard(data.hex2byte().bytes)
            riwayatList.append(riwayat!)
        }
    }
    
    func getTransactionTime(formatDate : String, formatTime : String) -> Date?{
        let dateFormatter2 = DateFormatter()
        dateFormatter2.dateFormat = "HHmmss"
        let date12 = dateFormatter2.date(from: formatTime)!

        dateFormatter2.dateFormat = "hh:mm a"
        let date22 = dateFormatter2.string(from: date12)
        
        let dateFormatter = DateFormatter()
          dateFormatter.locale = Locale(identifier: "en_US_POSIX") // set locale to reliable US_POSIX
          dateFormatter.dateFormat = "ddMMyy hh:mm a"
        return dateFormatter.date(from:(formatDate + " " + date22))
    }
    
    func getRealBalance(reverseHexa: String?) -> Int {
        guard let reverseHexa = reverseHexa?.trimmingCharacters(in: .whitespacesAndNewlines), !reverseHexa.isEmpty else {
            return 0
        }
        
        if reverseHexa.count % 2 != 0 {
            return 0
        }
        
        var sb = ""
        for l in stride(from: reverseHexa.count / 2, through: 1, by: -1) {
            let index1 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 2)
            let index2 = reverseHexa.index(reverseHexa.startIndex, offsetBy: l * 2 - 1)
            sb.append(reverseHexa[index1])
            sb.append(reverseHexa[index2])
        }
        return sb.hex2decimal()
    }
    
    private func updateScreen(){
        if (self.apduRunner.callback != nil){
            self.apduRunner.callback?.complete(emoney: self.emoney)
        }
    }

    private func resetHistoryState() {
        riwayatList.removeAll()
        mapv1 = ""
        start = 0
    }

    private func finalizeHistory() {
        riwayatList = riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
    }
    
    func riwayatCard(_ bArr: [UInt8]) -> RiwayatCard? {
        var str: String
        let riwayatCard = RiwayatCard()
        var wrap = Data(bArr)
        var bArr2 = [UInt8](repeating: 0, count: 6)
        var bArr3 = [UInt8](repeating: 0, count: 16)
        
        wrap.copyBytes(to: &bArr2, count: 6)
        wrap.removeFirst(10)
        
        let len = wrap.withUnsafeBytes { $0.load(as: Int32.self).bigEndian }
        wrap.removeFirst(4)
        let amount = wrap.withUnsafeBytes { $0.load(as: Int32.self).littleEndian }
        wrap.removeFirst(4)
        let desk = wrap.withUnsafeBytes { $0.load(as: Int32.self).littleEndian }
        wrap.removeFirst(4)
        
        do {
            wrap.copyBytes(to: &bArr3, count: 16)
            debugLog("bArr3: \(bArr3.map { String(format: "%02X", $0) }.joined())")
        } catch {
            debugLog("Error: \(error.localizedDescription)")
        }
        
        var type = 0
        if len == 288 {
            str = "payment".localizeString(string: self.langCode!)
            type = 1
        } else if len == 256 || len == 336 {
            type = 3
            str = "topup".localizeString(string: self.langCode!)
        } else {
            type = -1
            str = "unknown".localizeString(string: self.langCode!)
        }
        
        
        var str2 = ""
        for y in 0..<6 {
            str2 += String(format: "%02X", bArr2[y])
        }
        
        let transactionTime = self.getTransactionTime(formatDate: str.subString(from: 0, to: 6), formatTime: str.subString(from: 6, to: 12))
        riwayatCard.setTransactionTime(transactionTime!)
        riwayatCard.setAmount(Int(amount))
        
//        if str.caseInsensitiveCompare("payment") == .orderedSame {
//            type = 1
//        } else if str.caseInsensitiveCompare("topup") != .orderedSame {
//            type = 3
//        }
        
        riwayatCard.setProsesTipe(type)
        riwayatCard.setTitle(str)
        
        if type == -1 {
            return nil
        }
        
        return riwayatCard
    }
}
