//
//  RNVntelCCCDModule.swift
//  RNVntelCCCDModule
//
//  Copyright © 2022 Long Lee. All rights reserved.
//

import Foundation
import NFCPassportReader
import UniformTypeIdentifiers

@objc(RNVntelCCCD)
class RNVntelCCCDModule: RCTEventEmitter {
    enum Error: Swift.Error {
        // - MRZ_SCAN_FIRST
        // - NFC_NEED_GRANT_ACCESS
        // - NFC_IS_NOT_AVAILABLE
        // - NFC_IS_NOT_ENABLED
        case mrzScanFirst
        case nfcNeedFrantAccess
        case nfcIsNotAvaiable
        case nfcIsNotEnabled
        case nfcFailed
    }
    var hasObservers = false
    var mrzViews: Set<Int> = Set()
    private var mrz: MRZResult?
    private let passportReader = PassportReader()
    private let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .none
        return formatter
    }()
    
    @objc open override func supportedEvents() -> [String] {
        return ["ScanEvent"]
    }
    
    @objc
    override static func requiresMainQueueSetup() -> Bool {
        return true
    }
    
    @objc
    func addMRZView(_ tagId: Int,
                    resolver: @escaping RCTPromiseResolveBlock,
                    rejecter reject: @escaping RCTPromiseRejectBlock) {
        mrzViews.insert(tagId)
        resolver("OK")
        DispatchQueue.main.async {
            if let uiManager = self.bridge.module(for: RCTUIManager.self) as? RCTUIManager {
                if let view = uiManager.view(forReactTag: NSNumber(integerLiteral: tagId)) as? VntelCccdView  {
                    view.delegate = self
                }
            }
        }
    }
    
    @objc
    func removeMRZView(_ tagId: Int,
                       resolver: @escaping RCTPromiseResolveBlock,
                       rejecter reject: @escaping RCTPromiseRejectBlock) {
        mrzViews.remove(tagId)
    }
    @objc
    func startNFC(_ resolve: @escaping RCTPromiseResolveBlock,
                  rejecter reject: @escaping RCTPromiseRejectBlock) {
        guard let mrz = mrz, let dob = mrz.birthdate, let doe = mrz.expiryDate else {
            reject("", "", Error.mrzScanFirst)
            return
        }
        guard let documentNumber = mrz.documentNumber else {
            reject("", "", Error.mrzScanFirst)
            return
        }
        Task {
            self.sendEvent(event: "NFC_SCAN_BEGIN", params: ["payload": [:]])
            let ret = await scan(passportNumber: documentNumber, dateOfBirth: dob, dateOfExpiry: doe)
            switch ret {
            case .success(let data):
                self.sendEvent(event: "NFC_SCAN_FINISH", params: ["payload": data])
            case .failure(let error):
                self.sendEvent(event: "NFC_SCAN_ERROR", params: ["payload": ["error": error.localizedDescription]])
            }
        }
        resolve(nil)
    }
    
    override func startObserving() {
        hasObservers = true
    }
    
    override func stopObserving() {
        hasObservers = false
    }
    /// Send scan session info
    /// - Parameter params: 
    func sendEvent(event: String, params: [String: Any]) {
        if hasObservers {
            var body = params
            body["event"] = event
            self.sendEvent(withName: "ScanEvent", body: body)
        }
    }
}

extension RNVntelCCCDModule: VntelCccdViewDelegate {
    func onRecognizedMRZ(_ mrz: MRZResult) {
        self.mrz = mrz
        var birthdateString: String?
        var expiryDateString: String?
        if let birthdate = result.birthdate {
            birthdateString = dateFormatter.string(from: birthdate)
        }
        
        if let expiryDate = result.expiryDate {
            expiryDateString = dateFormatter.string(from: expiryDate)
        }
        
        sendEvent(event: "MRZ_SCAN_FINISH", params: ["payload": [
            "document_type": "\(mrz.documentType)",
            "country_code": mrz.countryCode,
            "surnames": mrz.surnames,
            "given_names": mrz.givenNames,
            "document_number": mrz.documentNumber ?? "-",
            "nationality_country_code": mrz.nationalityCountryCode,
            "birthdate": birthdateString ?? "-",
            "sex": "\(mrz.sex)",
            "expiry_date": expiryDateString ?? "-",
            "personal_number": mrz.optionalData ?? "-",
            "personal_number_2": mrz.optionalData2 ?? "-"
        ]])
    }
}

extension RNVntelCCCDModule {
    
    func scan(passportNumber: String, dateOfBirth : Date , dateOfExpiry: Date) async -> Result<[String: Any], Swift.Error> {
        
        let df = DateFormatter()
        df.timeZone = TimeZone(secondsFromGMT: 0)
        df.dateFormat = "YYMMdd"
        
        let pptNr = passportNumber
        let dob = df.string(from:dateOfBirth)
        let doe = df.string(from:dateOfExpiry)
        
        let passportUtils = PassportUtils()
        let mrzKey = passportUtils.getMRZKey( passportNumber: pptNr, dateOfBirth: dob, dateOfExpiry: doe)
        let masterListURL = Bundle(for: VntelCccdView.self).url(forResource: "masterList", withExtension: ".pem")!
        
        passportReader.setMasterListURL( masterListURL )
        passportReader.passiveAuthenticationUsesOpenSSL = true
        let customMessageHandler : (NFCViewDisplayMessage)->String? = { (displayMessage) in
            switch displayMessage {
            case .requestPresentPassport:
                return "Giữ iPhone của bạn gần CCCD hỗ trợ NFC."
            default:
                // Return nil for all other messages so we use the provided default
                return nil
            }
        }
        
        do {
            let passport = try await passportReader.readPassport( mrzKey: mrzKey, customDisplayMessage:customMessageHandler)
            var dict = passport.dumpPassportData(selectedDataGroups: DataGroupId.allCases, includeActiveAuthenticationData: true)
            dict.lowercaseKeys()
            return .success(dict)
        } catch {
            return .failure(error)
        }
    }
}
extension Dictionary where Key: ExpressibleByStringLiteral {
    public mutating func lowercaseKeys() {
        for key in self.keys {
            self[String(describing: key).lowercased() as! Key] = self.removeValue(forKey: key)
        }
    }
}
