//
//  TapCashApi.swift
//  Emoney Info
//
//  Created by Wira Irawan on 26/07/24.
//

import Foundation
import CoreNFC

public class TapCashApi : UnifiedNfcApi {
    var emoney : Emoney = Emoney()
    var tapCashData : TapCashData = TapCashData()
    var riwayatList: [RiwayatCard] = []
    var start = 0
    var totalLog = 0
    private var historyRetryCount = 0
    
    public override init() {}
    
    public func checkBalance(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.TAPCASH_APDU01, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.emoney.setCardLabel("BNI TapCash")
                self.tapCashData.setPurseData(response.getData().bytes)
                let balance = self.tapCashData.getPurseBalance()?.hexString().hex2decimal() ?? 0
                guard let can = self.tapCashData.getCAN()?.hexString() else {
                    self.emoney.setTampilRiwayat(false)
                    self.updateScreen()
                    self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
                    return
                }
                self.emoney.setBalance(balance)
                self.emoney.setCardNumber(can)
                self.totalLog = self.tapCashData.getTotalRecords()?.hexString().hex2decimal() ?? 0
                if (self.totalLog > 10){
                    self.totalLog = 10
                }
                debugLog("total log " + String(self.totalLog))
                self.historyRetryCount = 0
                self.startHistoryRead()
            } else {
                self.emoney.setTampilRiwayat(false)
                self.updateScreen()
                self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                self.apduRunner.invalidateSession()
            }
        })
    }

    private func startHistoryRead() {
        start = 0
        riwayatList.removeAll()
        getHistory(index: 0)
    }

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

    private func finalizeHistoryRead() {
        self.updateScreen()
        self.riwayatList = self.riwayatList.sorted {
            ($0.getTransationTime() ?? Date.distantPast) > ($1.getTransationTime() ?? Date.distantPast)
        }
        
        if (self.riwayatList.count > 0){
            self.emoney.setRiwayatList(self.riwayatList)
            self.emoney.setTampilRiwayat(true)
        }
        self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
        self.apduRunner.invalidateSession()
    }
    
    private func getHistory(index : Int){
        let st = String(index).leftPad(with: "0", length: 2)
        guard let payload = st.stringToBytes() else {
            debugLog("Invalid TapCash history index payload: \(st)")
            return
        }
        //90 32 03 00 01 00 10
        let TAPCASH_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_ : payload), expectedResponseLength : 16)
        debugLog(TAPCASH_LOG.toHexString())
        apduRunner.exchangeApdu(apduCommand: TAPCASH_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.addRiwayatTransaksi(data: response.getData().bytes)
            } else {
                self.retryHistoryRead(reason: "status \(response.sw1)-\(response.sw2) at index \(index)")
                return
            }
            self.start+=1
            if (self.start < (self.totalLog)){
                self.getHistory(index: self.start)
            } else {
                self.finalizeHistoryRead()
            }
        })
    }
    
    private func updateScreen(){
        if (self.apduRunner.callback != nil){
            self.apduRunner.callback?.complete(emoney: self.emoney)
        }
    }
    
    private func addRiwayatTransaksi(data: [UInt8]) {
        guard data.count >= 8 else {
            debugLog("TapCash history data too short: \(data.count)")
            return
        }
        let trxType = Array(data[0..<1])
        let trxAmount = Array(data[1..<4])
        let trxDateTimes = Array(data[4..<8])
        //let trxUserData = Array(data[8..<16])
        
        let title = trxType.hexString()
        var amount: Int
        if title.lowercased() == "01" || title.lowercased() == "05" || title.lowercased() == "07" || title.lowercased() == "10" || title.lowercased() == "20" {
            amount = trxAmount.hexString().secondComplementsAmount()
        } else {
            amount = trxType.hexString().hex2decimal()
        }
        
        let riwayatCard = RiwayatCard()
        riwayatCard.setTitle(getStatementTitle(header: trxType.hexString()))
        riwayatCard.setProsesTipe(getTranscationType(header: trxType.hexString()))
        riwayatCard.setAmount(amount)
        let transactionTime = self.getTransactionTime(julian: trxDateTimes.hexString())
        riwayatCard.setTransactionTime(transactionTime)
        riwayatList.append(riwayatCard)
    }
    
    private func getStatementTitle(header: String) -> String {
        var title = ""
        switch header.uppercased() {
        case "01":
            title = "payment".localizeString(string: self.langCode!)
        case "02":
            title = "Black List Card"
        case "03":
            title = "topup".localizeString(string: self.langCode!)
        case "04":
            title = "topup".localizeString(string: self.langCode!)
        case "05":
            title = "statementFee".localizeString(string: self.langCode!)
        case "06":
            title = "updateBalance".localizeString(string: self.langCode!)
        case "07":
            title = "gracePeriod".localizeString(string: self.langCode!)
        case "10":
            title = "refund".localizeString(string: self.langCode!)
        case "20":
            title = "refund".localizeString(string: self.langCode!)
        case "22":
            title = "close".localizeString(string: self.langCode!)
        case "F0":
            title = "atu".localizeString(string: self.langCode!)
        default:
            break
        }
        return title
    }
    
    private func getTranscationType(header: String) -> Int {
        switch header.uppercased() {
        case "01":
            return 1
        case "02":
            return 2
        case "03":
            return 0
        case "04":
            return 0
        case "05":
            return 1
        case "06":
            return 6
        case "07":
            return 7
        case "10":
            return 10
        case "20":
            return 20
        case "22":
            return 22
        default:
            break
        }
        return 0
    }
    
    func getTransactionTime(julian: String) -> Date {
        let dec = julian.hex2decimal()

        let cal = Calendar.current
        // set to 1st January 1995
        var dateComponents = DateComponents()
        dateComponents.year = 1995
        dateComponents.month = 1
        dateComponents.day = 1
        dateComponents.hour = 0
        dateComponents.minute = 0
        dateComponents.second = 0

        if let date = cal.date(from: dateComponents) {
            let newDate = date.addingTimeInterval(Double(dec))
            return newDate
        }
        
        return Date()
    }
    
}
