//
//  SLSSpan.m
//  AliyunLogProducer
//
//  Created by gordon on 2022/4/27.
//

#import "SLSSpan.h"
#import "SLSContextManager.h"
//#import "NSString+SLS.h"

NSString* const SLSINTERNAL = @"INTERNAL";
NSString* const SLSSERVER = @"SERVER";
NSString* const SLSCLIENT = @"CLIENT";
NSString* const SLSPRODUCER = @"PRODUCER";
NSString* const SLSCONSUMER = @"CONSUMER";

typedef void (^_internal_Scope)(void);

@interface SLSSpan ()
@property(nonatomic, strong, readonly) _internal_Scope scope;
@property(nonatomic, strong) NSLock *lock;
- (void) addEventInternal:(SLSEvent *)event;
@end

@implementation SLSSpan

- (instancetype)init
{
    self = [super init];
    if (self) {
        _attribute = [NSMutableDictionary<NSString*, NSString*> dictionary];
        _evetns = [NSMutableArray<SLSEvent*> array];
        _links = [NSMutableArray<SLSLink*> array];
        _resource = [[SLSResource alloc] init];
        _kind = SLSCLIENT;
        _isGlobal = YES;
        _lock = [[NSLock alloc] init];
    }

    return self;
}

- (instancetype) setParent: (SLSSpan *) parent {
    if (!parent) {
        return self;
    }
    
    [_lock lock];
    _parentSpanID = parent.spanID;
    _traceID = parent.traceID;
    [_lock unlock];
    return self;
}

- (SLSSpan *) addAttribute:(SLSAttribute *)attribute, ... NS_REQUIRES_NIL_TERMINATION {
    [_lock lock];
    NSMutableDictionary<NSString*, NSString*> *dict = (NSMutableDictionary<NSString*, NSString*> *) _attribute;
    [dict setObject:attribute.value forKey:attribute.key];
    va_list args;
    SLSAttribute *arg;
    va_start(args, attribute);
    while ((arg = va_arg(args, SLSAttribute*))) {
        [dict setObject:arg.value forKey:arg.key];
    }
    va_end(args);
    [_lock unlock];
    return self;
}

- (SLSSpan *) addAttributes:(NSArray<SLSAttribute*> *)attributes {
    [_lock lock];
    NSMutableDictionary<NSString*, NSString*> *dict = (NSMutableDictionary<NSString*, NSString*> *) _attribute;
    
    for (SLSAttribute *attr in attributes) {
        [dict setObject:attr.value forKey:attr.key];
    }
    [_lock unlock];
    return self;
}
- (SLSSpan *) addResource: (SLSResource *) resource {
    if (resource) {
        [_lock lock];
        [_resource merge:resource];
        [_lock unlock];
    }
    
    return self;
}
- (SLSSpan *) addEvent:(NSString *)name {
    [self addEventInternal:[SLSEvent eventWithName:name]];
    return self;
}
- (SLSSpan *) addEvent:(NSString *)name attribute: (SLSAttribute *)attribute, ... NS_REQUIRES_NIL_TERMINATION {
    SLSEvent *event = [SLSEvent eventWithName:name];
    [event addAttribute:attribute, nil];

    va_list args;
    SLSAttribute *arg;
    va_start(args, attribute);
    while ((arg = va_arg(args, SLSAttribute*))) {
        [event addAttribute:arg, nil];
    }
    va_end(args);

    [self addEventInternal:event];
    return self;
}
- (SLSSpan *) addEvent:(NSString *)name attributes:(NSArray<SLSAttribute *> *)attributes {
    [self addEventInternal:
         [[SLSEvent eventWithName:name] addAttributes:attributes]
    ];
    return self;
}

- (SLSSpan *) addLink: (SLSLink *)link, ... NS_REQUIRES_NIL_TERMINATION {
    NSMutableArray<SLSLink*> *links = (NSMutableArray<SLSLink*> *)_links;
    [_lock lock];
    [links addObject:link];
    [_lock unlock];
    
    va_list args;
    SLSLink *arg;
    va_start(args, link);
    while ((arg = va_arg(args, SLSLink*))) {
        [_lock lock];
        [links addObject:arg];
        [_lock unlock];
    }
    va_end(args);
    
    return self;
}
- (SLSSpan *) addLinks: (NSArray<SLSLink *> *)links {
    if (nil == links) {
        return self;
    }
    [_lock lock];
    [((NSMutableArray<SLSLink*> *)_links) addObjectsFromArray:links];
    [_lock unlock];
    return self;
}

- (SLSSpan *) recordException:(NSException *)exception {
    return [self recordException:exception attributes:[NSArray array]];
}
- (SLSSpan *) recordException:(NSException *)exception attribute: (SLSAttribute *)attribute, ... NS_REQUIRES_NIL_TERMINATION {
    NSMutableArray<SLSAttribute *> *attr = [NSMutableArray array];
    if (nil != attribute) {
        [attr addObject:attribute];
    }

    va_list args;
    SLSAttribute *arg;
    va_start(args, attribute);
    while ((arg = va_arg(args, SLSAttribute*))) {
        [attr addObject:arg];
    }
    va_end(args);

    return [self recordException:exception attributes:attr];
}
- (SLSSpan *) recordException:(NSException *)exception attributes:(NSArray<SLSAttribute *> *)attribute {
    SLSEvent *event = [[SLSEvent eventWithName:@"exception"] addAttribute:
                           [SLSAttribute of:@"exception.type" value:exception.name],
                           [SLSAttribute of:@"exception.message" value:exception.reason],
                           [SLSAttribute of:@"exception.stacktrace" value:(exception.callStackSymbols ? [[exception.callStackSymbols valueForKey:@"description"] componentsJoinedByString:@"\n"] : @"")],
                           nil
    ];

    [event addAttributes:attribute];

    [self addEventInternal:event];
    return self;
}

- (void) addEventInternal:(SLSEvent *)event {
    [_lock lock];
    [((NSMutableArray<SLSEvent*> *) _evetns) addObject:event];
    [_lock unlock];
}

- (BOOL) end {
    [_lock lock];
    if (_isEnd) {
        return NO;
    }
    _isEnd = YES;
    
    _duration = (_end - _start) / 1000;
    if (nil != _scope) {
        _scope();
    }
    [_lock unlock];
    return YES;
}

- (NSDictionary<NSString*, NSString*> *) toDict {
    [_lock lock];
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    
    [dict setObject:_name forKey:@"name"];
    [dict setObject:_kind forKey:@"kind"];
    [dict setObject:_traceID forKey:@"traceID"];
    [dict setObject:_spanID forKey:@"spanID"];
    [dict setObject:_parentSpanID ? _parentSpanID : @"" forKey:@"parentSpanID"];
    [dict setObject:_sessionId ? _sessionId : @"" forKey:@"sid"];
    [dict setObject:_transactionId ? _transactionId : @"" forKey:@"pid"];
    [dict setObject:[NSString stringWithFormat:@"%ld", _start] forKey:@"start"];
    [dict setObject:[NSString stringWithFormat:@"%ld", _duration] forKey:@"duration"];
    [dict setObject:[NSString stringWithFormat:@"%ld", _end] forKey:@"end"];
    [dict setObject:_statusCode == UNSET ? @"UNSET" : (_statusCode == OK ? @"OK" : @"ERROR" ) forKey:@"statusCode"];
    [dict setObject:_statusMessage.length > 0 ? _statusMessage : @"" forKey:@"statusMessage"];
    [dict setObject:_host ? _host : @"" forKey:@"host"];
    // service name default: iOS
    [dict setObject:_service.length > 0 ? _service : @"iOS" forKey:@"service"];
    
    NSMutableDictionary<NSString*, NSString*> *attributeDict = [NSMutableDictionary<NSString*, NSString*> dictionary];
    for (NSString* key in [_attribute allKeys]) {
        [attributeDict setObject:[_attribute valueForKey:key] forKey:key];
    }
    
    [dict setObject:[self stringWithDictionary:attributeDict] forKey:@"attribute"];
    
    if (_resource.attributes) {
        NSMutableDictionary<NSString*, NSString*> *resourceDict = [NSMutableDictionary<NSString*, NSString*> dictionary];
        for (SLSAttribute *attr in _resource.attributes) {
            [resourceDict setObject:attr.value forKey:attr.key];
        }

        [dict setObject:[self stringWithDictionary: resourceDict] forKey:@"resource"];
    }
    
    if (_evetns && [_evetns count] > 0) {
        NSMutableArray *logs = [NSMutableArray array];
        for (SLSEvent *event in _evetns) {
            NSMutableDictionary *object = [NSMutableDictionary dictionary];
            [object setObject:(event.name.length > 0 ? event.name : @"") forKey:@"name"];
            [object setObject:[[NSNumber numberWithLong:event.epochNanos] stringValue] forKey:@"epochNanos"];
            [object setObject:[[NSNumber numberWithInt:event.totalAttributeCount] stringValue] forKey:@"totalAttributeCount"];
            
            NSArray<SLSAttribute *> *attributes = event.attributes;
            NSMutableDictionary<NSString*, NSString*> *attrObject = [NSMutableDictionary dictionary];
            for (SLSAttribute *attr in attributes) {
                [attrObject setObject:attr.value forKey:attr.key];
            }
            [object setObject:attrObject forKey:@"attributes"];
            
            [logs addObject:object];
        }

        [dict setObject:logs forKey:@"logs"];
    }
    
    if (_links && _links.count > 0) {
        NSMutableArray *links = [NSMutableArray array];
        for (SLSLink *link in _links) {
            NSMutableDictionary *object = [NSMutableDictionary dictionary];
            [object setObject:(link.traceId.length > 0 ? link.traceId : @"") forKey:@"traceID"];
            [object setObject:(link.spanId.length > 0 ? link.spanId : @"") forKey:@"spanID"];
            
            NSArray<SLSAttribute *> *attributes = link.attributes;
            NSMutableDictionary<NSString*, NSString*> *attrObject = [NSMutableDictionary dictionary];
            for (SLSAttribute *attr in attributes) {
                [attrObject setObject:attr.value forKey:attr.key];
            }
            [object setObject:attrObject forKey:@"attributes"];
            
            [links addObject:object];
        }
        
        [dict setObject:links forKey:@"links"];
    }
    [_lock unlock];
    return dict;
}

- (SLSSpan *) setGlobal: (BOOL) global {
    [_lock lock];
    _isGlobal = global;
    [_lock unlock];
    return self;
}

- (SLSSpan *) setScope: (void (^)(void)) scope {
    [_lock lock];
    _scope = scope;
    [_lock unlock];
    return self;
}

- (id)copyWithZone:(nullable NSZone *)zone {
    SLSSpan *span = [[SLSSpan alloc] init];

    [_lock lock];
    span.name = _name;
    span.traceID = _traceID;
    span.spanID = _spanID;
    span.parentSpanID = _parentSpanID;
    span.start = _start;
    span.end = _end;
    span.duration = _duration;
    span.attribute = _attribute;
    span.statusCode = _statusCode;
    span.statusMessage = _statusMessage;
    span.host = _host;
    span.resource = [_resource copy];
    span.service = _service;
    span.sessionId = _sessionId;
    span.transactionId = _transactionId;
    span->_isEnd = _isEnd;
    [_lock unlock];
    return span;
}

- (NSString *) stringWithDictionary: (NSDictionary *) dictionary {
    if (![NSJSONSerialization isValidJSONObject:dictionary]) {
        return [NSString string];
    }
    
    NSJSONWritingOptions options = kNilOptions;
    if (@available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *)) {
        options = NSJSONWritingSortedKeys;
    }
    NSError *error = nil;
    NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary
                                                   options:options
                                                     error:&error
    ];
    
    if (nil != error) {
        return [NSString string];
    }
    
    return [[NSString alloc] initWithData:data
                                 encoding:NSUTF8StringEncoding
    ];
}

@end
