//
//  VFYAuditModel.m
//  Verif-y Neo
//
//  Created by MTN on 10/02/21.
//

#import "VFYAuditModel.h"
#import "NSDictionary+DictCategory.h"

// Shorthand for simple blocks
#define λ(decl, expr) (^(decl) { return (expr); })

// nil → NSNull conversion for JSON dictionaries
static id NSNullify(id _Nullable x) {
    return (x == nil || x == NSNull.null) ? NSNull.null : x;
}

NS_ASSUME_NONNULL_BEGIN

#pragma mark - Private model interfaces

@interface VFYAuditModel (JSONConversion)
+ (instancetype)fromJSONDictionary:(NSDictionary *)dict;
- (NSDictionary *)JSONDictionary;
@end

@interface VFYDeviceDetailLog (JSONConversion)
+ (instancetype)fromJSONDictionary:(NSDictionary *)dict;
- (NSDictionary *)JSONDictionary;
@end

@interface VFYDocExtractionLog (JSONConversion)
+ (instancetype)fromJSONDictionary:(NSDictionary *)dict;
- (NSDictionary *)JSONDictionary;
@end

@interface VFYErrorDetail (JSONConversion)
+ (instancetype)fromJSONDictionary:(NSDictionary *)dict;
- (NSDictionary *)JSONDictionary;
@end

@interface VFYFieldComparisonList (JSONConversion)
+ (instancetype)fromJSONDictionary:(NSDictionary *)dict;
- (NSDictionary *)JSONDictionary;
@end

static id map(id collection, id (^f)(id value)) {
    id result = nil;
    if ([collection isKindOfClass:NSArray.class]) {
        result = [NSMutableArray arrayWithCapacity:[collection count]];
        for (id x in collection) [result addObject:f(x)];
    } else if ([collection isKindOfClass:NSDictionary.class]) {
        result = [NSMutableDictionary dictionaryWithCapacity:[collection count]];
        for (id key in collection) [result setObject:f([collection objectForKey:key]) forKey:key];
    }
    return result;
}

#pragma mark - JSON serialization

VFYAuditModel *_Nullable VFYAuditFromData(NSData *data, NSError **error)
{
    @try {
        id json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:error];
        return *error ? nil : [VFYAuditModel fromJSONDictionary:json];
    } @catch (NSException *exception) {
        *error = [NSError errorWithDomain:@"JSONSerialization" code:-1 userInfo:@{ @"exception": exception }];
        return nil;
    }
}

VFYAuditModel *_Nullable VFYAuditFromJSON(NSString *json, NSStringEncoding encoding, NSError **error)
{
    return VFYAuditFromData([json dataUsingEncoding:encoding], error);
}

NSData *_Nullable VFYAuditToData(VFYAuditModel *audit, NSError **error)
{
    @try {
        id json = [audit JSONDictionary];
        id tempDict = [json dictionaryRemovingNSNullValues]; //MTN 02/22/2021 to remove the null values from dictionary
        NSData *data = [NSJSONSerialization dataWithJSONObject:tempDict options:kNilOptions error:error];
        return *error ? nil : data;
    } @catch (NSException *exception) {
        *error = [NSError errorWithDomain:@"JSONSerialization" code:-1 userInfo:@{ @"exception": exception }];
        return nil;
    }
}

NSString *_Nullable VFYAuditToJSON(VFYAuditModel *audit, NSStringEncoding encoding, NSError **error)
{
    NSData *data = VFYAuditToData(audit, error);
    return data ? [[NSString alloc] initWithData:data encoding:encoding] : nil;
}

@implementation VFYAuditModel
+ (NSDictionary<NSString *, NSString *> *)properties
{
    static NSDictionary<NSString *, NSString *> *properties;
    return properties = properties ? properties : @{
        @"deviceDetailLog": @"deviceDetailLog",
        @"docExtractionLog": @"docExtractionLog",
        @"errorDetail": @"errorDetail",
        @"eventDuration": @"eventDuration",
        @"eventStatus": @"eventStatus",
        @"eventType": @"eventType",
        @"fieldComparisonList": @"fieldComparisonList",
        @"isRetry": @"isRetry",
        @"previousEventId": @"previousEventID",
        @"requestId": @"requestID",
    };
}

+ (_Nullable instancetype)fromData:(NSData *)data error:(NSError *_Nullable *)error
{
    return VFYAuditFromData(data, error);
}

+ (_Nullable instancetype)fromJSON:(NSString *)json encoding:(NSStringEncoding)encoding error:(NSError *_Nullable *)error
{
    return VFYAuditFromJSON(json, encoding, error);
}

+ (instancetype)fromJSONDictionary:(NSDictionary *)dict
{
    return dict ? [[VFYAuditModel alloc] initWithJSONDictionary:dict] : nil;
}

- (instancetype)initWithJSONDictionary:(NSDictionary *)dict
{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
        _deviceDetailLog = [VFYDeviceDetailLog fromJSONDictionary:(id)_deviceDetailLog];
        _docExtractionLog = [VFYDocExtractionLog fromJSONDictionary:(id)_docExtractionLog];
        _errorDetail = [VFYErrorDetail fromJSONDictionary:(id)_errorDetail];
        _fieldComparisonList = map(_fieldComparisonList, λ(id x, [VFYFieldComparisonList fromJSONDictionary:x]));
    }
    return self;
}

- (void)setValue:(nullable id)value forKey:(NSString *)key
{
    id resolved = VFYAuditModel.properties[key];
    if (resolved) [super setValue:value forKey:resolved];
}

- (void)setNilValueForKey:(NSString *)key
{
    id resolved = VFYAuditModel.properties[key];
    if (resolved) [super setValue:@(0) forKey:resolved];
}

- (NSDictionary *)JSONDictionary
{
    id dict = [[self dictionaryWithValuesForKeys:VFYAuditModel.properties.allValues] mutableCopy];

    // Rewrite property names that differ in JSON
    for (id jsonName in VFYAuditModel.properties) {
        id propertyName = VFYAuditModel.properties[jsonName];
        if (![jsonName isEqualToString:propertyName]) {
            dict[jsonName] = dict[propertyName];
            [dict removeObjectForKey:propertyName];
        }
    }

    // Map values that need translation
    [dict addEntriesFromDictionary:@{
        @"deviceDetailLog": [_deviceDetailLog JSONDictionary],
        @"docExtractionLog": [_docExtractionLog JSONDictionary],
        @"errorDetail": [_errorDetail JSONDictionary],
        @"fieldComparisonList": map(_fieldComparisonList, λ(id x, [x JSONDictionary])),
        @"isRetry": _isRetry ? @YES : @NO,
    }];

    return dict;
}

- (NSData *_Nullable)toData:(NSError *_Nullable *)error
{
    return VFYAuditToData(self, error);
}

- (NSString *_Nullable)toJSON:(NSStringEncoding)encoding error:(NSError *_Nullable *)error
{
    return VFYAuditToJSON(self, encoding, error);
}
@end

@implementation VFYDeviceDetailLog
+ (NSDictionary<NSString *, NSString *> *)properties
{
    static NSDictionary<NSString *, NSString *> *properties;
    return properties = properties ? properties : @{
        @"appIdentifier": @"appIdentifier",
        @"createDate": @"createDate",
        @"deviceModel": @"deviceModel",
        @"language": @"language",
        @"location": @"location",
        @"osVersion": @"osVersion",
    };
}

+ (instancetype)fromJSONDictionary:(NSDictionary *)dict
{
    return dict ? [[VFYDeviceDetailLog alloc] initWithJSONDictionary:dict] : nil;
}

- (instancetype)initWithJSONDictionary:(NSDictionary *)dict
{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

- (void)setValue:(nullable id)value forKey:(NSString *)key
{
    id resolved = VFYDeviceDetailLog.properties[key];
    if (resolved) [super setValue:value forKey:resolved];
}

- (void)setNilValueForKey:(NSString *)key
{
    id resolved = VFYDeviceDetailLog.properties[key];
    if (resolved) [super setValue:@(0) forKey:resolved];
}

- (NSDictionary *)JSONDictionary
{
    return [self dictionaryWithValuesForKeys:VFYDeviceDetailLog.properties.allValues];
}
@end

@implementation VFYDocExtractionLog
+ (NSDictionary<NSString *, NSString *> *)properties
{
    static NSDictionary<NSString *, NSString *> *properties;
    return properties = properties ? properties : @{
        @"docBackRef": @"docBackRef",
        @"docFrontRef": @"docFrontRef",
        @"docType": @"docType",
        @"docTypeName": @"docTypeName",
        @"extractionChannel": @"extractionChannel",
        @"extractionDuration": @"extractionDuration",
        @"extractionId": @"extractionID",
        @"extractionStatus": @"extractionStatus",
    };
}

+ (instancetype)fromJSONDictionary:(NSDictionary *)dict
{
    return dict ? [[VFYDocExtractionLog alloc] initWithJSONDictionary:dict] : nil;
}

- (instancetype)initWithJSONDictionary:(NSDictionary *)dict
{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

- (void)setValue:(nullable id)value forKey:(NSString *)key
{
    id resolved = VFYDocExtractionLog.properties[key];
    if (resolved) [super setValue:value forKey:resolved];
}

- (void)setNilValueForKey:(NSString *)key
{
    id resolved = VFYDocExtractionLog.properties[key];
    if (resolved) [super setValue:@(0) forKey:resolved];
}

- (NSDictionary *)JSONDictionary
{
    id dict = [[self dictionaryWithValuesForKeys:VFYDocExtractionLog.properties.allValues] mutableCopy];

    // Rewrite property names that differ in JSON
    for (id jsonName in VFYDocExtractionLog.properties) {
        id propertyName = VFYDocExtractionLog.properties[jsonName];
        if (![jsonName isEqualToString:propertyName]) {
            dict[jsonName] = dict[propertyName];
            [dict removeObjectForKey:propertyName];
        }
    }

    return dict;
}
@end

@implementation VFYErrorDetail
+ (NSDictionary<NSString *, NSString *> *)properties
{
    static NSDictionary<NSString *, NSString *> *properties;
    return properties = properties ? properties : @{
        @"errorCode": @"errorCode",
        @"errorMsg": @"errorMsg",
        @"eventId": @"eventID",
        @"serviceRequestId": @"serviceRequestID",
    };
}

+ (instancetype)fromJSONDictionary:(NSDictionary *)dict
{
    return dict ? [[VFYErrorDetail alloc] initWithJSONDictionary:dict] : nil;
}

- (instancetype)initWithJSONDictionary:(NSDictionary *)dict
{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

- (void)setValue:(nullable id)value forKey:(NSString *)key
{
    id resolved = VFYErrorDetail.properties[key];
    if (resolved) [super setValue:value forKey:resolved];
}

- (void)setNilValueForKey:(NSString *)key
{
    id resolved = VFYErrorDetail.properties[key];
    if (resolved) [super setValue:@(0) forKey:resolved];
}

- (NSDictionary *)JSONDictionary
{
    id dict = [[self dictionaryWithValuesForKeys:VFYErrorDetail.properties.allValues] mutableCopy];

    // Rewrite property names that differ in JSON
    for (id jsonName in VFYErrorDetail.properties) {
        id propertyName = VFYErrorDetail.properties[jsonName];
        if (![jsonName isEqualToString:propertyName]) {
            dict[jsonName] = dict[propertyName];
            [dict removeObjectForKey:propertyName];
        }
    }

    return dict;
}
@end

@implementation VFYFieldComparisonList
+ (NSDictionary<NSString *, NSString *> *)properties
{
    static NSDictionary<NSString *, NSString *> *properties;
    return properties = properties ? properties : @{
        @"comparisonScore": @"comparisonScore",
        @"comparisonStatus": @"comparisonStatus",
        @"isEditedByUser": @"isEditedByUser",
        @"isExtracted": @"isExtracted",
        @"isNameSuggested": @"isNameSuggested",
        @"sourceFieldName": @"sourceFieldName",
        @"sourceFieldValue": @"sourceFieldValue",
        @"targetFieldName": @"targetFieldName",
        @"targetFieldValue": @"targetFieldValue",
        @"validationMessage": @"validationMessage",
    };
}

+ (instancetype)fromJSONDictionary:(NSDictionary *)dict
{
    return dict ? [[VFYFieldComparisonList alloc] initWithJSONDictionary:dict] : nil;
}

- (instancetype)initWithJSONDictionary:(NSDictionary *)dict
{
    if (self = [super init]) {
        [self setValuesForKeysWithDictionary:dict];
    }
    return self;
}

- (void)setValue:(nullable id)value forKey:(NSString *)key
{
    id resolved = VFYFieldComparisonList.properties[key];
    if (resolved) [super setValue:value forKey:resolved];
}

- (void)setNilValueForKey:(NSString *)key
{
    id resolved = VFYFieldComparisonList.properties[key];
    if (resolved) [super setValue:@(0) forKey:resolved];
}

- (NSDictionary *)JSONDictionary
{
    id dict = [[self dictionaryWithValuesForKeys:VFYFieldComparisonList.properties.allValues] mutableCopy];

    // Map values that need translation
    [dict addEntriesFromDictionary:@{
        @"isEditedByUser": _isEditedByUser ? @YES : @NO,
        @"isExtracted": _isExtracted ? @YES : @NO,
        @"isNameSuggested": _isNameSuggested ? @YES : @NO,
    }];

    return dict;
}
@end

NS_ASSUME_NONNULL_END
