//
//  EncounterV2Mapping.swift
//  CovidSafe
//
//  Copyright © 2020 Australian Government. All rights reserved.
//

import CoreData

struct MigrationRemoteBlob: Encodable {
    enum CodingKeys: String, CodingKey {
        case msg
        case modelC
        case modelP
        case rssi
        case txPower
    }
    var msg: String?
    var modelC: String?
    var modelP: String?
    var rssi: NSNumber?
    var txPower: NSNumber?
    
    func encode(to encoder: Encoder) throws {
        
        var container = encoder.container(keyedBy: CodingKeys.self)
        if let msg = msg {
            try container.encode(msg, forKey: .msg)
        }
        
        if let modelC = modelC, modelC != BluetraceConfig.DummyModel{
            try container.encode(modelC, forKey: .modelC)
        }
        if let modelP = modelP, modelP != BluetraceConfig.DummyModel {
            try container.encode(modelP, forKey: .modelP)
        }
        if let rssi = rssi, rssi.intValue != BluetraceConfig.DummyRSSI {
            try container.encode(rssi.doubleValue, forKey: .rssi)
        }
        if let txPower = txPower, txPower.intValue != BluetraceConfig.DummyTxPower {
            try container.encode(txPower.doubleValue, forKey: .txPower)
        }
    }
    
}


class EncounterV2Mapping: NSEntityMigrationPolicy {
    var localEmptyBlob: String = ""
    override func begin(_ mapping: NSEntityMapping, with manager: NSMigrationManager) throws {
        try super.begin(mapping, with: manager)
        let emptyLocal = try JSONEncoder().encode(MigrationRemoteBlob())
        localEmptyBlob = try Crypto.encrypt(dataToEncrypt: emptyLocal)
    }
    override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
        
        if (sInstance.entity.name == "Encounter") {
            guard let version = sInstance.primitiveValue(forKey: "v") as? Int64 else {
                return
            }
            let encounterV2 = NSEntityDescription.insertNewObject(forEntityName: "Encounter", into: manager.destinationContext)
            let msg = sInstance.primitiveValue(forKey: "msg") as? String
            let modelC = sInstance.primitiveValue(forKey: "modelC") as? String
            let modelP = sInstance.primitiveValue(forKey: "modelP") as? String
            let rssi = sInstance.primitiveValue(forKey: "rssi") as? NSNumber
            let txPower = sInstance.primitiveValue(forKey: "txPower") as? NSNumber
            let org = sInstance.primitiveValue(forKey: "org") as? String
            let timestamp = sInstance.primitiveValue(forKey: "timestamp") as? Date
            
            
            encounterV2.setPrimitiveValue(org, forKey: "org")
            encounterV2.setPrimitiveValue(BluetraceConfig.ProtocolVersion, forKey: "v")
            encounterV2.setPrimitiveValue(timestamp, forKey: "timestamp")
            do {
                try autoreleasepool {
                    if version == 1 {
                        //convert a regular old v1 entry
                        encounterV2.setPrimitiveValue(localEmptyBlob, forKey: "localBlob")
                        let remoteBlob = MigrationRemoteBlob(msg: msg, modelC: modelC, modelP: modelP, rssi: rssi, txPower: txPower)
                        
                        let blobJson = try JSONEncoder().encode(remoteBlob)
                        let remoteBlobEncrypted = try Crypto.encrypt(dataToEncrypt: blobJson)
                        encounterV2.setPrimitiveValue(remoteBlobEncrypted, forKey: "remoteBlob")
                        manager.associate(sourceInstance: sInstance, withDestinationInstance: encounterV2, for: mapping)
                        
                    } else if version == 2 {
                        //convert an entry that was recieved from an already updated app (the msg will already be encrypted)
                        let localMigrationBlob = MigrationRemoteBlob(msg: nil, modelC: modelC, modelP: modelP, rssi: rssi, txPower: txPower)
                        let jsonLocal = try JSONEncoder().encode(localMigrationBlob)
                        let localBlob = try Crypto.encrypt(dataToEncrypt: jsonLocal)
                        encounterV2.setPrimitiveValue(localBlob, forKey: "localBlob")
                        encounterV2.setPrimitiveValue(msg, forKey: "remoteBlob")
                        manager.associate(sourceInstance: sInstance, withDestinationInstance: encounterV2, for: mapping)
                    }
                }
            } catch {
                throw(error)
            }
            // we're dropping all the debug messages save to the db during the migration as they're unnessecary
        }
    }
}