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

import Foundation
import CoreNFC

public class BrizziApi : UnifiedNfcApi {
    var emoney : Emoney = Emoney()
    var riwayatList: [RiwayatCard] = []
    var uid : String?
    var rawLog : String = ""
    private var historyRetryCount = 0
    
    public override init() {}
    
    public func getUid(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_UID01, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0xAF){
                self.getUid02()
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func getUid02(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_UID02, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0xAF){
                self.getUid02()
            } else {
                self.uid = response.getData().hexEncodedString().subString(from: 0, to: 14)
                self.getCardNumber()
            }
        })
    }
    
    private func getCardNumber(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU01, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0x00){
                self.emoney.setCardLabel("Brizzi")
                self.emoney.setCardNumber(response.getData().hexEncodedString().subString(from: 6, to: 22))
                self.process01()
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func process01(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU02, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0x00){
                self.process02()
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func process02(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU03, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){
                self.process03(data: response.getData())
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func process03(data : Data){
        let brizziSamHelper = BrizziSamHelper()
        brizziSamHelper.keyCard = data.hexEncodedString()
        let random = "8DC0DC40FE1DC582CF7099E2AACFBC10".hex2byte()
        guard let uid = self.uid else {
            self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            return
        }
        let command = self.emoney.getCardNumber() + uid + "FF"
        guard let decrypted = BrizziSamHelper.decryptDeSeDe(random)?.hexEncodedString() else {
            self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            return
        }
        
        let decryptedFinal = decrypted.subString(from: 0, to: 32)
        guard decryptedFinal.count == 32,
              let encrypted = BrizziSamHelper.encryptDeSeDe(command, decryptedFinal, "0000000000000000")?.hexEncodedString() else {
            self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            return
        }
        
        brizziSamHelper.encryptedKey = encrypted.subString(from: 0, to: 32)
        
        
        let randomHex = "3C37029CA595FE4E7E62FCB2F7909B2C".hex2byte()
        let randomHexDecrypted = BrizziSamHelper.decryptDeSeDe(randomHex)
        let randomHexFinal = randomHexDecrypted?.hexEncodedString().subString(from: 0, to: 32) ?? ""
        
        guard randomHexFinal.count == 32,
              let encryptedKey = brizziSamHelper.encryptedKey,
              let randomHexEncrypted = BrizziSamHelper.encryptDeSeDe(encryptedKey, randomHexFinal, brizziSamHelper.authKey)?.hexEncodedString() else {
            self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            return
        }
        brizziSamHelper.random = randomHexEncrypted.subString(from: 0, to: 32)
        
        
        let samChallenge = brizziSamHelper.generateSamRandom().subString(from: 0, to: 32)
        guard samChallenge.count == 32 else {
            self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            return
        }
        let BRI_APDU04 = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0xAF, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(_: samChallenge.hex2byte()), expectedResponseLength : CommonConstants.LE_GET_ALL_RESPONSE_DATA)
        apduRunner.exchangeApdu(apduCommand: BRI_APDU04, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){
                self.process04()
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func process04(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_APDU05, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){
                self.emoney.setBalance(self.getRealBalance(reverseHexa: response.getData().hexEncodedString().subString(from: 0, to: 8)))
                self.historyRetryCount = 0
                self.startHistoryRead()
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }

    private func startHistoryRead() {
        rawLog = ""
        riwayatList.removeAll()
        getLog()
    }

    private func retryHistoryRead(reason: String) {
        if historyRetryCount < 1 {
            historyRetryCount += 1
            debugLog("Retrying Brizzi history read: \(reason)")
            startHistoryRead()
        } else {
            finalizeHistoryRead()
        }
    }

    private func finalizeHistoryRead() {
        if (self.parseLog()){
            self.riwayatList = self.riwayatList.sorted {
                ($0.getTransationTime() ?? Date.distantPast) > ($1.getTransationTime() ?? Date.distantPast)
            }
            self.emoney.setRiwayatList(self.riwayatList)
            self.emoney.setTampilRiwayat(true)
        }
        self.updateScreen()
        self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
        self.apduRunner.invalidateSession()
    }
    
    private func getLog(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_LOG01, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x91 && response.sw2 == 0xAF){
                self.rawLog.append(response.getData().hexEncodedString())
                self.getMoreLog()
            } else {
                self.retryHistoryRead(reason: "initial log status \(response.sw1)-\(response.sw2)")
            }
        })
    }

    private func getMoreLog(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BRI_LOG02, completionHandler: {response in
            if (response.sw1 == 0x91 && response.sw2 == 0xAF){
                self.rawLog.append(response.getData().hexEncodedString())
                self.getMoreLog()
            } else {
                if (response.sw1 == 0x91 && response.sw2 == 0x00) || (response.sw1 == 0x90 && response.sw2 == 0x00) {
                    self.rawLog.append(response.getData().hexEncodedString())
                    self.finalizeHistoryRead()
                } else {
                    self.retryHistoryRead(reason: "continuation log status \(response.sw1)-\(response.sw2)")
                }
            }
        })
    }
    
    private func parseLog() -> Bool {
        let logs = self.rawLog.trimmingCharacters(in: .whitespacesAndNewlines)
        if (logs.count % 64 != 0){
            return false
        }
        let total = logs.count/64
        for i in 0..<total {
            let start = i*64
            let end = start + 64
            let data = logs.subString(from: start, to: end)
            let riwayat = RiwayatCard()
//            riwayat.setLocationId(data.subString(from: 16, to: 32))
//            riwayat.setLocationName(data.subString(from: 0, to: 16))
            riwayat.setTitle(self.getCode(trxCode: data.subString(from: 44, to: 46)))
            riwayat.setProsesTipe(self.getTipe(trxCode: data.subString(from: 44, to: 46)))
            riwayat.setAmount(self.getRealBalance(reverseHexa: data.subString(from: 46, to: 52)))
            
            let time = self.getTransactionTime(formatDate: data.subString(from: 32, to: 38), formatTime: data.subString(from: 38, to: 44))
            guard let time else {
                debugLog("Skipping Brizzi log with invalid timestamp: \(data)")
                continue
            }
            riwayat.setTransactionTime(time)
            
            riwayatList.append(riwayat)
        }
        
        return true
    }
    
    func getTransactionTime(formatDate : String, formatTime : String) -> Date?{
        let dateFormatter2 = DateFormatter()
        dateFormatter2.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter2.dateFormat = "HHmmss"
        guard let date12 = dateFormatter2.date(from: formatTime) else {
            return nil
        }

        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))
    }
    
    private func getCode(trxCode: String) -> String {
        let trxUp = trxCode.uppercased()
        if trxUp.contains("5F") {
            return "reactivation".localizeString(string: self.langCode!)
        } else if trxUp.contains("EB") {
            return "payment".localizeString(string: self.langCode!)
        } else if trxUp.contains("EC") {
            return "topup".localizeString(string: self.langCode!)
        } else if trxUp.contains("ED") {
            return "void".localizeString(string: self.langCode!)
        } else if trxUp.contains("EF") {
            return "updateBalance".localizeString(string: self.langCode!)
        } else {
            return "-"
        }
    }
    
    private func getTipe(trxCode: String) -> Int {
        let trxUp = trxCode.uppercased()
        if trxUp.contains("5F") {
            return 2
        } else if trxUp.contains("EB") {
            return 1
        } else if trxUp.contains("EC") {
            return 0
        } else if trxUp.contains("ED") {
            return 0
        } else if trxUp.contains("EF") {
            return 1
        } else {
            return 0
        }
    }

    
    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)
        }
    }
}
