/// <inheritdoc/>
        public virtual JObject EncryptPayload(byte[] payload)
        {
            using (MetricsUtil.MetricsInstance.Measure.Timer.Time(EncryptTimerOptions))
            {
                EnvelopeEncryptResult result = WithIntermediateKeyForWrite(intermediateCryptoKey => crypto.EnvelopeEncrypt(
                                                                               payload,
                                                                               intermediateCryptoKey,
                                                                               new KeyMeta(partition.IntermediateKeyId, intermediateCryptoKey.GetCreated())));

                KeyMeta parentKeyMeta = (KeyMeta)result.UserState;

                EnvelopeKeyRecord keyRecord =
                    new EnvelopeKeyRecord(DateTimeOffset.UtcNow, parentKeyMeta, result.EncryptedKey);

                Json wrapperDocument = new Json();
                wrapperDocument.Put("Key", keyRecord.ToJson());
                wrapperDocument.Put("Data", result.CipherText);

                return(wrapperDocument.ToJObject());
            }
        }
        internal virtual CryptoKey GetLatestOrCreateSystemKey()
        {
            Option <EnvelopeKeyRecord> newestSystemKeyRecord = LoadLatestKeyRecord(partition.SystemKeyId);

            if (newestSystemKeyRecord.IsSome)
            {
                EnvelopeKeyRecord keyRecord = (EnvelopeKeyRecord)newestSystemKeyRecord;

                // If the key we just got back isn't expired, then just use it
                if (!IsKeyExpiredOrRevoked(keyRecord))
                {
                    return(keyManagementService.DecryptKey(
                               keyRecord.EncryptedKey, keyRecord.Created, keyRecord.Revoked.IfNone(false)));
                }

                // If we're here we know we have an expired key.
                // If we're doing queued rotation, flag it and continue to use the expired key
                if (cryptoPolicy.IsQueuedKeyRotation())
                {
                    // TODO : Queued rotation
                    Logger.LogDebug("Queuing up SK {keyId} for rotation", partition.SystemKeyId);
                    return(keyManagementService.DecryptKey(
                               keyRecord.EncryptedKey, keyRecord.Created, keyRecord.Revoked.IfNone(false)));
                }

                // If we're here then we're doing inline rotation and have an expired key.
                // Fall through as if we didn't have the key
            }

            DateTimeOffset systemKeyCreated = cryptoPolicy.TruncateToSystemKeyPrecision(DateTimeOffset.UtcNow);
            CryptoKey      systemKey        = crypto.GenerateKey(systemKeyCreated);

            try
            {
                EnvelopeKeyRecord newSystemKeyRecord = new EnvelopeKeyRecord(
                    systemKey.GetCreated(), null, keyManagementService.EncryptKey(systemKey), false);

                Logger.LogDebug(
                    "Attempting to store new SK {keyId} for created {created}",
                    partition.SystemKeyId,
                    newSystemKeyRecord.Created);
                if (metastore.Store(partition.SystemKeyId, newSystemKeyRecord.Created, newSystemKeyRecord.ToJson()))
                {
                    return(systemKey);
                }
                else
                {
                    Logger.LogDebug(
                        "Attempted to store new SK {keyId} but detected duplicate for created {created}, disposing newly created SK",
                        partition.SystemKeyId,
                        systemKey.GetCreated());
                    DisposeKey(systemKey, null);
                }
            }
            catch (Exception e)
            {
                DisposeKey(systemKey, e);
                throw new AppEncryptionException("Unable to store new System Key", e);
            }

            // If we're here, storing of the newly generated key failed above which means we attempted to
            // save a duplicate key to the metastore. If that's the case, then we know a valid key exists
            // in the metastore, so let's grab it and return it.
            newestSystemKeyRecord = LoadLatestKeyRecord(partition.SystemKeyId);

            if (newestSystemKeyRecord.IsSome)
            {
                EnvelopeKeyRecord keyRecord = (EnvelopeKeyRecord)newestSystemKeyRecord;
                return(keyManagementService.DecryptKey(
                           keyRecord.EncryptedKey, keyRecord.Created, keyRecord.Revoked.IfNone(false)));
            }
            else
            {
                throw new AppEncryptionException("SystemKey not present after LoadLatestKeyRecord retry");
            }
        }
        internal virtual CryptoKey GetLatestOrCreateIntermediateKey()
        {
            Option <EnvelopeKeyRecord> newestIntermediateKeyRecord = LoadLatestKeyRecord(partition.IntermediateKeyId);

            if (newestIntermediateKeyRecord.IsSome)
            {
                EnvelopeKeyRecord keyRecord = (EnvelopeKeyRecord)newestIntermediateKeyRecord;

                // If the key we just got back isn't expired, then just use it
                if (!IsKeyExpiredOrRevoked(keyRecord))
                {
                    try
                    {
                        return(WithExistingSystemKey(
                                   keyRecord.ParentKeyMeta.IfNone(() => throw new MetadataMissingException(
                                                                      "Could not find parentKeyMeta (SK) for intermediateKey ")),
                                   true,
                                   key => DecryptKey(keyRecord, key)));
                    }
                    catch (MetadataMissingException e)
                    {
                        Logger.LogDebug(
                            e,
                            "The SK for the IK ({keyId}, {created}) is missing or in an invalid state. Will create new IK instead.",
                            partition.IntermediateKeyId,
                            keyRecord.Created);
                    }
                }

                // If we're here we know we have an expired key.
                // If we're doing queued rotation, flag it and continue to use the expired key
                if (cryptoPolicy.IsQueuedKeyRotation())
                {
                    // TODO : Queued rotation
                    Logger.LogDebug("Queuing up IK {keyId} for rotation", partition.IntermediateKeyId);
                    try
                    {
                        return(WithExistingSystemKey(
                                   keyRecord.ParentKeyMeta.IfNone(() =>
                                                                  throw new MetadataMissingException(
                                                                      "Could not find parentKeyMeta (SK) for intermediateKey")),
                                   true,
                                   key => DecryptKey(keyRecord, key)));
                    }
                    catch (MetadataMissingException e)
                    {
                        Logger.LogDebug(
                            e,
                            "The SK for the IK ({keyId}, {created}) is missing or in an invalid state. Will create new IK instead.",
                            partition.IntermediateKeyId,
                            keyRecord.Created);
                    }
                }

                // If we're here then we're doing inline rotation and have an expired key.
                // Fall through as if we didn't have the key
            }

            DateTimeOffset intermediateKeyCreated = cryptoPolicy.TruncateToIntermediateKeyPrecision(DateTimeOffset.UtcNow);
            CryptoKey      intermediateKey        = crypto.GenerateKey(intermediateKeyCreated);

            try
            {
                EnvelopeKeyRecord newIntermediateKeyRecord = WithSystemKeyForWrite(systemCryptoKey =>
                                                                                   new EnvelopeKeyRecord(
                                                                                       intermediateKey.GetCreated(),
                                                                                       new KeyMeta(partition.SystemKeyId, systemCryptoKey.GetCreated()),
                                                                                       crypto.EncryptKey(intermediateKey, systemCryptoKey),
                                                                                       false));

                Logger.LogDebug(
                    "Attempting to store new IK {keyId}, for created {created}",
                    partition.IntermediateKeyId,
                    newIntermediateKeyRecord.Created);

                if (metastore.Store(
                        partition.IntermediateKeyId, newIntermediateKeyRecord.Created, newIntermediateKeyRecord.ToJson()))
                {
                    return(intermediateKey);
                }
                else
                {
                    Logger.LogDebug(
                        "Attempted to store new IK {keyId} but detected duplicate for created {created}, disposing newly created IK",
                        partition.IntermediateKeyId,
                        intermediateKey.GetCreated());
                    DisposeKey(intermediateKey, null);
                }
            }
            catch (Exception e)
            {
                DisposeKey(intermediateKey, e);
                throw new AppEncryptionException("Unable to store new Intermediate Key", e);
            }

            // If we're here, storing of the newly generated key failed above which means we attempted to
            // save a duplicate key to the metastore. If that's the case, then we know a valid key exists
            // in the metastore, so let's grab it and return it.

            // Using a new variable instead of the one above because the WithSystemKeyForWrite use above wants finality
            Option <EnvelopeKeyRecord> actualLatestIntermediateKeyRecord = LoadLatestKeyRecord(partition.IntermediateKeyId);

            if (actualLatestIntermediateKeyRecord.IsSome)
            {
                EnvelopeKeyRecord keyRecord = (EnvelopeKeyRecord)actualLatestIntermediateKeyRecord;

                // NOTE: Not wrapping this in try/catch to allow errors to bubble up. If we're missing meta in this flow, something's wrong.
                return(WithExistingSystemKey(
                           keyRecord.ParentKeyMeta.IfNone(() =>
                                                          throw new MetadataMissingException("Could not find parentKeyMeta (SK) for intermediateKey")),
                           true,
                           key => DecryptKey(keyRecord, key)));
            }
            else
            {
                throw new AppEncryptionException("IntermediateKey not present after LoadLatestKeyRecord retry");
            }
        }