#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import "Knotapi.h"

// Test-only visibility for private helpers and properties
@interface Knotapi (Testing)
@property (nonatomic, assign) BOOL isBridgeActive;
@property (nonatomic, assign) BOOL isClosing;
- (BOOL)isBridgeReadyForEvents;
- (NSDictionary *)createSafeDictionaryFromDictionary:(NSDictionary *)dictionary;
- (void)addSafeStringValue:(NSString *)value forKey:(NSString *)key toDictionary:(NSMutableDictionary *)dictionary;
- (void)addSafeMetaData:(NSDictionary *)metaData forKey:(NSString *)key toDictionary:(NSMutableDictionary *)dictionary;
- (void)sendSafeEventWithName:(NSString *)eventName body:(NSDictionary *)body;
// RCTEventEmitter API used in expectations
- (void)sendEventWithName:(NSString *)eventName body:(id)body;
@end

/**
 * KnotapiHelperTests - Tests for helper methods and safety logic
 *
 * This test suite focuses on testing the private helper methods that ensure
 * safe data handling and prevent crashes from nil/NSNull values.
 */
@interface KnotapiHelperTests : XCTestCase
@property (nonatomic, strong) Knotapi *knotapi;
@end

@implementation KnotapiHelperTests

- (void)setUp {
    [super setUp];
    self.knotapi = [[Knotapi alloc] init];
}

- (void)tearDown {
    self.knotapi = nil;
    [super tearDown];
}

#pragma mark - Bridge State Tests

/**
 * Test bridge state initialization
 */
- (void)testBridgeStateInitialization {
    // Given: A new Knotapi instance
    Knotapi *knotapi = [[Knotapi alloc] init];

    // Then: Bridge should be active and not closing
    XCTAssertTrue(knotapi.isBridgeActive, @"Bridge should be active on initialization");
    XCTAssertFalse(knotapi.isClosing, @"Bridge should not be closing on initialization");
}

/**
 * Test isBridgeReadyForEvents method
 */
- (void)testIsBridgeReadyForEvents {
    // Test case 1: Bridge active, not closing
    self.knotapi.isBridgeActive = YES;
    self.knotapi.isClosing = NO;
    XCTAssertTrue([self.knotapi isBridgeReadyForEvents], @"Bridge should be ready when active and not closing");

    // Test case 2: Bridge inactive, not closing
    self.knotapi.isBridgeActive = NO;
    self.knotapi.isClosing = NO;
    XCTAssertFalse([self.knotapi isBridgeReadyForEvents], @"Bridge should not be ready when inactive");

    // Test case 3: Bridge active, closing
    self.knotapi.isBridgeActive = YES;
    self.knotapi.isClosing = YES;
    XCTAssertFalse([self.knotapi isBridgeReadyForEvents], @"Bridge should not be ready when closing");

    // Test case 4: Bridge inactive, closing
    self.knotapi.isBridgeActive = NO;
    self.knotapi.isClosing = YES;
    XCTAssertFalse([self.knotapi isBridgeReadyForEvents], @"Bridge should not be ready when inactive and closing");
}

#pragma mark - Dictionary Safety Tests

/**
 * Test createSafeDictionaryFromDictionary with valid data
 */
- (void)testCreateSafeDictionaryWithValidData {
    // Given: A dictionary with valid data
    NSDictionary *inputDict = @{
        @"stringKey": @"stringValue",
        @"numberKey": @42,
        @"boolKey": @YES,
        @"arrayKey": @[@"item1", @"item2"],
        @"dictKey": @{@"nestedKey": @"nestedValue"}
    };

    // When: Creating a safe dictionary
    NSDictionary *result = [self.knotapi createSafeDictionaryFromDictionary:inputDict];

    // Then: Result should be identical to input
    XCTAssertEqualObjects(result, inputDict, @"Valid dictionary should be returned unchanged");
}

/**
 * Test createSafeDictionaryFromDictionary with nil values
 */
- (void)testCreateSafeDictionaryWithNilValues {
    // Given: A dictionary with nil values
    NSMutableDictionary *inputDict = [NSMutableDictionary dictionary];
    inputDict[@"validKey"] = @"validValue";
    inputDict[@"nilKey"] = nil;
    inputDict[@"anotherValidKey"] = @"anotherValidValue";

    // When: Creating a safe dictionary
    NSDictionary *result = [self.knotapi createSafeDictionaryFromDictionary:inputDict];

    // Then: Only valid key-value pairs should be included
    NSDictionary *expectedResult = @{@"validKey": @"validValue", @"anotherValidKey": @"anotherValidValue"};
    XCTAssertEqualObjects(result, expectedResult, @"Nil values should be filtered out");
}

/**
 * Test createSafeDictionaryFromDictionary with NSNull values
 */
- (void)testCreateSafeDictionaryWithNSNullValues {
    // Given: A dictionary with NSNull values
    NSMutableDictionary *inputDict = [NSMutableDictionary dictionary];
    inputDict[@"validKey"] = @"validValue";
    inputDict[@"nullKey"] = [NSNull null];
    inputDict[@"anotherValidKey"] = @"anotherValidValue";
    inputDict[@"anotherNullKey"] = [NSNull null];

    // When: Creating a safe dictionary
    NSDictionary *result = [self.knotapi createSafeDictionaryFromDictionary:inputDict];

    // Then: Only valid key-value pairs should be included
    NSDictionary *expectedResult = @{@"validKey": @"validValue", @"anotherValidKey": @"anotherValidValue"};
    XCTAssertEqualObjects(result, expectedResult, @"NSNull values should be filtered out");
}

/**
 * Test createSafeDictionaryFromDictionary with mixed invalid data
 */
- (void)testCreateSafeDictionaryWithMixedInvalidData {
    // Given: A dictionary with mixed valid and invalid data
    NSMutableDictionary *inputDict = [NSMutableDictionary dictionary];
    inputDict[@"validString"] = @"validValue";
    inputDict[@"validNumber"] = @42;
    inputDict[@"nilValue"] = nil;
    inputDict[@"nullValue"] = [NSNull null];
    inputDict[@"validBool"] = @YES;
    inputDict[@"nilKey"] = @"value"; // Regular key/value; should be preserved

    // When: Creating a safe dictionary
    NSDictionary *result = [self.knotapi createSafeDictionaryFromDictionary:inputDict];

    // Then: Only valid key-value pairs should be included
    NSDictionary *expectedResult = @{@"validString": @"validValue", @"validNumber": @42, @"validBool": @YES, @"nilKey": @"value"};
    XCTAssertEqualObjects(result, expectedResult, @"Only valid key-value pairs should be included");
}

/**
 * Test createSafeDictionaryFromDictionary with nil input
 */
- (void)testCreateSafeDictionaryWithNilInput {
    // Given: Nil input
    NSDictionary *inputDict = nil;

    // When: Creating a safe dictionary
    NSDictionary *result = [self.knotapi createSafeDictionaryFromDictionary:inputDict];

    // Then: Empty dictionary should be returned
    XCTAssertEqualObjects(result, @{}, @"Nil input should return empty dictionary");
}

/**
 * Test createSafeDictionaryFromDictionary with non-dictionary input
 */
- (void)testCreateSafeDictionaryWithNonDictionaryInput {
    // Given: Non-dictionary input
    NSString *inputString = @"not a dictionary";

    // When: Creating a safe dictionary
    NSDictionary *result = [self.knotapi createSafeDictionaryFromDictionary:(NSDictionary *)inputString];

    // Then: Empty dictionary should be returned
    XCTAssertEqualObjects(result, @{}, @"Non-dictionary input should return empty dictionary");
}

#pragma mark - String Safety Tests

/**
 * Test addSafeStringValue with valid string
 */
- (void)testAddSafeStringValueWithValidString {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding a valid string
    [self.knotapi addSafeStringValue:@"testValue" forKey:@"testKey" toDictionary:dict];

    // Then: String should be added
    XCTAssertEqualObjects(dict[@"testKey"], @"testValue", @"Valid string should be added");
}

/**
 * Test addSafeStringValue with nil value
 */
- (void)testAddSafeStringValueWithNilValue {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding a nil value
    [self.knotapi addSafeStringValue:nil forKey:@"testKey" toDictionary:dict];

    // Then: Nothing should be added
    XCTAssertNil(dict[@"testKey"], @"Nil value should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test addSafeStringValue with NSNull value
 */
- (void)testAddSafeStringValueWithNSNullValue {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding an NSNull value
    [self.knotapi addSafeStringValue:(NSString *)[NSNull null] forKey:@"testKey" toDictionary:dict];

    // Then: Nothing should be added
    XCTAssertNil(dict[@"testKey"], @"NSNull value should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test addSafeStringValue with non-string value
 */
- (void)testAddSafeStringValueWithNonStringValue {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding a non-string value
    [self.knotapi addSafeStringValue:(NSString *)@42 forKey:@"testKey" toDictionary:dict];

    // Then: Nothing should be added
    XCTAssertNil(dict[@"testKey"], @"Non-string value should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test addSafeStringValue with nil key
 */
- (void)testAddSafeStringValueWithNilKey {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding a value with nil key
    [self.knotapi addSafeStringValue:@"testValue" forKey:nil toDictionary:dict];

    // Then: Nothing should be added
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test addSafeStringValue with empty string
 */
- (void)testAddSafeStringValueWithEmptyString {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding an empty string
    [self.knotapi addSafeStringValue:@"" forKey:@"testKey" toDictionary:dict];

    // Then: Empty string should be added (it's still a valid string)
    XCTAssertEqualObjects(dict[@"testKey"], @"", @"Empty string should be added");
}

#pragma mark - Metadata Safety Tests

/**
 * Test addSafeMetaData with valid metadata
 */
- (void)testAddSafeMetaDataWithValidData {
    // Given: An empty dictionary and valid metadata
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    NSDictionary *validMetaData = @{@"key1": @"value1", @"key2": @"value2"};

    // When: Adding valid metadata
    [self.knotapi addSafeMetaData:validMetaData forKey:@"metaData" toDictionary:dict];

    // Then: Metadata should be added
    XCTAssertEqualObjects(dict[@"metaData"], validMetaData, @"Valid metadata should be added");
}

/**
 * Test addSafeMetaData with metadata containing NSNull
 */
- (void)testAddSafeMetaDataWithNSNullValues {
    // Given: An empty dictionary and metadata with NSNull
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    NSMutableDictionary *metaDataWithNull = [NSMutableDictionary dictionary];
    metaDataWithNull[@"key1"] = @"value1";
    metaDataWithNull[@"key2"] = [NSNull null];
    metaDataWithNull[@"key3"] = @"value3";

    // When: Adding metadata with NSNull
    [self.knotapi addSafeMetaData:metaDataWithNull forKey:@"metaData" toDictionary:dict];

    // Then: Only valid values should be included
    NSDictionary *expectedMetaData = @{@"key1": @"value1", @"key3": @"value3"};
    XCTAssertEqualObjects(dict[@"metaData"], expectedMetaData, @"NSNull values should be filtered from metadata");
}

/**
 * Test addSafeMetaData with nil metadata
 */
- (void)testAddSafeMetaDataWithNilMetadata {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding nil metadata
    [self.knotapi addSafeMetaData:nil forKey:@"metaData" toDictionary:dict];

    // Then: Nothing should be added
    XCTAssertNil(dict[@"metaData"], @"Nil metadata should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test addSafeMetaData with NSNull metadata
 */
- (void)testAddSafeMetaDataWithNSNullMetadata {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding NSNull metadata
    [self.knotapi addSafeMetaData:(NSDictionary *)[NSNull null] forKey:@"metaData" toDictionary:dict];

    // Then: Nothing should be added
    XCTAssertNil(dict[@"metaData"], @"NSNull metadata should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test addSafeMetaData with empty metadata
 */
- (void)testAddSafeMetaDataWithEmptyMetadata {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding empty metadata
    [self.knotapi addSafeMetaData:@{} forKey:@"metaData" toDictionary:dict];

    // Then: Nothing should be added (empty metadata is filtered out)
    XCTAssertNil(dict[@"metaData"], @"Empty metadata should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test addSafeMetaData with non-dictionary metadata
 */
- (void)testAddSafeMetaDataWithNonDictionaryMetadata {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // When: Adding non-dictionary metadata
    [self.knotapi addSafeMetaData:(NSDictionary *)@"not a dictionary" forKey:@"metaData" toDictionary:dict];

    // Then: Nothing should be added
    XCTAssertNil(dict[@"metaData"], @"Non-dictionary metadata should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test addSafeMetaData with nil key
 */
- (void)testAddSafeMetaDataWithNilKey {
    // Given: An empty dictionary
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    NSDictionary *validMetaData = @{@"key1": @"value1"};

    // When: Adding metadata with nil key
    [self.knotapi addSafeMetaData:validMetaData forKey:nil toDictionary:dict];

    // Then: Nothing should be added
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

#pragma mark - Send Safe Event Tests

/**
 * Test sendSafeEventWithName with valid data
 */
- (void)testSendSafeEventWithValidData {
    // Given: Valid event name and body
    NSString *eventName = @"testEvent";
    NSDictionary *body = @{@"key1": @"value1", @"key2": @"value2"};

    // Ensure bridge is ready
    self.knotapi.isBridgeActive = YES;
    self.knotapi.isClosing = NO;
    // Create partial mock to spy on sendEventWithName calls
    Knotapi *mockKnotapi = OCMPartialMock(self.knotapi);
    OCMExpect([mockKnotapi sendEventWithName:eventName body:body]);

    // When: Sending safe event
    [mockKnotapi sendSafeEventWithName:eventName body:body];

    // Then: Event should be sent
    OCMVerifyAll(mockKnotapi);
}

/**
 * Test sendSafeEventWithName with body containing NSNull
 */
- (void)testSendSafeEventWithNSNullInBody {
    // Given: Event name and body with NSNull
    NSString *eventName = @"testEvent";
    NSMutableDictionary *bodyWithNull = [NSMutableDictionary dictionary];
    bodyWithNull[@"key1"] = @"value1";
    bodyWithNull[@"key2"] = [NSNull null];
    bodyWithNull[@"key3"] = @"value3";

    // Ensure bridge is ready
    self.knotapi.isBridgeActive = YES;
    self.knotapi.isClosing = NO;
    // Create partial mock to spy on sendEventWithName calls
    Knotapi *mockKnotapi = OCMPartialMock(self.knotapi);
    OCMExpect([mockKnotapi sendEventWithName:eventName body:[OCMArg checkWithBlock:^BOOL(NSDictionary *body) {
        // Body should have NSNull values filtered out
        return [body[@"key1"] isEqualToString:@"value1"] &&
               body[@"key2"] == nil &&
               [body[@"key3"] isEqualToString:@"value3"];
    }]]);

    // When: Sending safe event
    [mockKnotapi sendSafeEventWithName:eventName body:bodyWithNull];

    // Then: Event should be sent with cleaned body
    OCMVerifyAll(mockKnotapi);
}

/**
 * Test sendSafeEventWithName with nil event name
 */
- (void)testSendSafeEventWithNilEventName {
    // Given: Nil event name
    NSString *eventName = nil;
    NSDictionary *body = @{@"key1": @"value1"};

    // Ensure bridge is ready
    self.knotapi.isBridgeActive = YES;
    self.knotapi.isClosing = NO;
    // Create partial mock to spy on sendEventWithName calls
    Knotapi *mockKnotapi = OCMPartialMock(self.knotapi);
    OCMReject([mockKnotapi sendEventWithName:[OCMArg any] body:[OCMArg any]]);

    // When: Sending safe event with nil name
    [mockKnotapi sendSafeEventWithName:eventName body:body];

    // Then: No event should be sent
    OCMVerifyAll(mockKnotapi);
}

/**
 * Test sendSafeEventWithName with nil body
 */
- (void)testSendSafeEventWithNilBody {
    // Given: Valid event name and nil body
    NSString *eventName = @"testEvent";
    NSDictionary *body = nil;

    // Ensure bridge is ready
    self.knotapi.isBridgeActive = YES;
    self.knotapi.isClosing = NO;
    // Create partial mock to spy on sendEventWithName calls
    Knotapi *mockKnotapi = OCMPartialMock(self.knotapi);
    OCMReject([mockKnotapi sendEventWithName:[OCMArg any] body:[OCMArg any]]);

    // When: Sending safe event with nil body
    [mockKnotapi sendSafeEventWithName:eventName body:body];

    // Then: No event should be sent
    OCMVerifyAll(mockKnotapi);
}

#pragma mark - Edge Case Tests

/**
 * Test helper methods with extreme edge cases
 */
- (void)testHelperMethodsWithExtremeEdgeCases {
    // Test createSafeDictionaryFromDictionary with very large dictionary
    NSMutableDictionary *largeDict = [NSMutableDictionary dictionary];
    for (int i = 0; i < 1000; i++) {
        if (i % 3 == 0) {
            largeDict[[NSString stringWithFormat:@"key%d", i]] = [NSString stringWithFormat:@"value%d", i];
        } else if (i % 3 == 1) {
            largeDict[[NSString stringWithFormat:@"key%d", i]] = [NSNull null];
        } else {
            largeDict[[NSString stringWithFormat:@"key%d", i]] = nil;
        }
    }

    NSDictionary *result = [self.knotapi createSafeDictionaryFromDictionary:largeDict];
    XCTAssertNotNil(result, @"Large dictionary should be processed");
    XCTAssertTrue(result.count > 0, @"Some valid entries should remain");
    XCTAssertTrue(result.count < largeDict.count, @"Some entries should be filtered out");

    // Test addSafeStringValue with very long string
    NSMutableString *longString = [NSMutableString string];
    for (int i = 0; i < 10000; i++) {
        [longString appendString:@"a"];
    }

    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [self.knotapi addSafeStringValue:longString forKey:@"longKey" toDictionary:dict];
    XCTAssertEqualObjects(dict[@"longKey"], longString, @"Long string should be handled correctly");
}

/**
 * Test helper methods with special characters
 */
- (void)testHelperMethodsWithSpecialCharacters {
    // Test with strings containing special characters
    NSArray *specialStrings = @[
        @"String with spaces",
        @"String\nwith\nnewlines",
        @"String\twith\ttabs",
        @"String with \"quotes\"",
        @"String with 'single quotes'",
        @"String with émojis 🚀",
        @"String with unicode: 你好世界",
        @"String with symbols: !@#$%^&*()",
        @"",
        @"   ",
        @"\n\t\r"
    ];

    for (NSString *specialString in specialStrings) {
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        [self.knotapi addSafeStringValue:specialString forKey:@"specialKey" toDictionary:dict];
        XCTAssertEqualObjects(dict[@"specialKey"], specialString, @"Special string should be handled correctly: %@", specialString);
    }
}

/**
 * Test nil metadata handling
 */
- (void)testNilMetadataHandling {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // Test with nil metadata
    [self.knotapi addSafeMetaData:nil forKey:@"nilMeta" toDictionary:dict];
    XCTAssertNil(dict[@"nilMeta"], @"Nil metadata should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");

    // Test with NSNull metadata
    [self.knotapi addSafeMetaData:(NSDictionary *)[NSNull null] forKey:@"nullMeta" toDictionary:dict];
    XCTAssertNil(dict[@"nullMeta"], @"NSNull metadata should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test nested nil values in metadata
 */
- (void)testNestedNilValuesInMetadata {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // Create metadata with nested nil values
    NSMutableDictionary *nestedMeta = [NSMutableDictionary dictionary];
    nestedMeta[@"validKey"] = @"validValue";
    nestedMeta[@"nilValue"] = nil;
    nestedMeta[@"nullValue"] = [NSNull null];
    NSMutableDictionary *inner = [NSMutableDictionary dictionary];
    inner[@"innerValid"] = @"innerValue";
    inner[@"innerNil"] = nil; // noop (removes key if present)
    inner[@"innerNull"] = [NSNull null];
    nestedMeta[@"nestedDict"] = inner;
    nestedMeta[@"nestedArray"] = @[@"arrayValue", [NSNull null], @"anotherValue"];

    [self.knotapi addSafeMetaData:nestedMeta forKey:@"nestedMeta" toDictionary:dict];

    NSDictionary *resultMeta = dict[@"nestedMeta"];
    XCTAssertNotNil(resultMeta, @"Nested metadata should be added");
    XCTAssertEqual(resultMeta.count, 3, @"Only valid values should remain");
    XCTAssertEqualObjects(resultMeta[@"validKey"], @"validValue", @"Valid key should be preserved");
    XCTAssertNotNil(resultMeta[@"nestedDict"], @"Nested dictionary should be preserved");

    // Verify nested dictionary is cleaned
    NSDictionary *nestedDict = resultMeta[@"nestedDict"];
    XCTAssertEqual(nestedDict.count, 1, @"Nested dictionary should have only valid values");
    XCTAssertEqualObjects(nestedDict[@"innerValid"], @"innerValue", @"Inner valid value should be preserved");

    // Verify nested array is cleaned and NSNull values are removed
    NSArray *nestedArray = resultMeta[@"nestedArray"];
    XCTAssertNotNil(nestedArray, @"Nested array should be preserved");
    XCTAssertEqual(nestedArray.count, 2, @"Array should have only valid values (NSNull and nil filtered out)");
    XCTAssertEqualObjects(nestedArray[0], @"arrayValue", @"First valid array element should be preserved");
    XCTAssertEqualObjects(nestedArray[1], @"anotherValue", @"Second valid array element should be preserved");
}

/**
 * Test deeply nested nil values
 */
- (void)testDeeplyNestedNilValues {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // Create deeply nested metadata with nil values
    NSMutableDictionary *deepNestedMeta = [NSMutableDictionary dictionary];
    NSMutableDictionary *level3 = [NSMutableDictionary dictionary];
    level3[@"valid"] = @"value";
    level3[@"nil"] = nil; // noop
    level3[@"null"] = [NSNull null];

    NSMutableDictionary *level2 = [NSMutableDictionary dictionary];
    level2[@"level3"] = level3;
    level2[@"level2nil"] = nil; // noop
    level2[@"level2null"] = [NSNull null];

    NSMutableDictionary *level1Dict = [NSMutableDictionary dictionary];
    level1Dict[@"level2"] = level2;
    level1Dict[@"level1nil"] = nil; // noop
    level1Dict[@"level1null"] = [NSNull null];

    deepNestedMeta[@"level1"] = level1Dict;

    [self.knotapi addSafeMetaData:deepNestedMeta forKey:@"deepMeta" toDictionary:dict];

    NSDictionary *resultMeta = dict[@"deepMeta"];
    XCTAssertNotNil(resultMeta, @"Deeply nested metadata should be added");
    XCTAssertEqual(resultMeta.count, 1, @"Only level1 should remain");
    XCTAssertNotNil(resultMeta[@"level1"], @"Level1 should be preserved");

    // Verify level1 is cleaned
    NSDictionary *level1 = resultMeta[@"level1"];
    XCTAssertEqual(level1.count, 1, @"Level1 should have only valid values");
    XCTAssertNotNil(level1[@"level2"], @"Level2 should be preserved");

    // Verify level2 is cleaned
    NSDictionary *level2 = level1[@"level2"];
    XCTAssertEqual(level2.count, 1, @"Level2 should have only valid values");
    XCTAssertNotNil(level2[@"level3"], @"Level3 should be preserved");

    // Verify level3 is cleaned
    NSDictionary *level3 = level2[@"level3"];
    XCTAssertEqual(level3.count, 1, @"Level3 should have only valid values");
    XCTAssertEqualObjects(level3[@"valid"], @"value", @"Valid value should be preserved");
}

/**
 * Test metadata with only nil/NSNull values
 */
- (void)testMetadataWithOnlyNilValues {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // Create metadata with only nil/NSNull values
    NSMutableDictionary *allNilMeta = [NSMutableDictionary dictionary];
    allNilMeta[@"nil1"] = nil;
    allNilMeta[@"null1"] = [NSNull null];
    allNilMeta[@"nil2"] = nil;
    allNilMeta[@"null2"] = [NSNull null];

    [self.knotapi addSafeMetaData:allNilMeta forKey:@"allNilMeta" toDictionary:dict];

    // Should not be added since all values are nil/NSNull
    XCTAssertNil(dict[@"allNilMeta"], @"Metadata with only nil/NSNull values should not be added");
    XCTAssertEqual(dict.count, 0, @"Dictionary should remain empty");
}

/**
 * Test array NSNull filtering specifically
 */
- (void)testArrayNSNullFiltering {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // Create metadata with arrays containing NSNull values
    NSMutableDictionary *arrayMeta = [NSMutableDictionary dictionary];
    arrayMeta[@"validString"] = @"validValue";

    // Array with mixed valid and NSNull values
    arrayMeta[@"mixedArray"] = @[@"valid1", [NSNull null], @"valid2", [NSNull null], @"valid3"];

    // Array with only NSNull values
    arrayMeta[@"nullOnlyArray"] = @[[NSNull null], [NSNull null]];

    // Array with only valid values
    arrayMeta[@"validOnlyArray"] = @[@"valid1", @"valid2", @"valid3"];

    // Nested array with NSNull
    arrayMeta[@"nestedArray"] = @[
        @[@"nested1", [NSNull null], @"nested2"],
        @[[NSNull null], [NSNull null]],
        @[@"nested3"]
    ];

    [self.knotapi addSafeMetaData:arrayMeta forKey:@"arrayMeta" toDictionary:dict];

    NSDictionary *resultMeta = dict[@"arrayMeta"];
    XCTAssertNotNil(resultMeta, @"Array metadata should be added");

    // Verify mixed array filtering
    NSArray *mixedArray = resultMeta[@"mixedArray"];
    XCTAssertNotNil(mixedArray, @"Mixed array should be preserved");
    XCTAssertEqual(mixedArray.count, 3, @"Mixed array should have only valid values");
    XCTAssertEqualObjects(mixedArray[0], @"valid1", @"First valid element should be preserved");
    XCTAssertEqualObjects(mixedArray[1], @"valid2", @"Second valid element should be preserved");
    XCTAssertEqualObjects(mixedArray[2], @"valid3", @"Third valid element should be preserved");

    // Verify null-only array is filtered out
    NSArray *nullOnlyArray = resultMeta[@"nullOnlyArray"];
    XCTAssertNil(nullOnlyArray, @"Array with only NSNull values should be filtered out");

    // Verify valid-only array is preserved
    NSArray *validOnlyArray = resultMeta[@"validOnlyArray"];
    XCTAssertNotNil(validOnlyArray, @"Valid-only array should be preserved");
    XCTAssertEqual(validOnlyArray.count, 3, @"Valid-only array should have all elements");

    // Verify nested array filtering
    NSArray *nestedArray = resultMeta[@"nestedArray"];
    XCTAssertNotNil(nestedArray, @"Nested array should be preserved");
    XCTAssertEqual(nestedArray.count, 2, @"Nested array should have only valid sub-arrays");

    // First sub-array should have 2 valid elements
    NSArray *firstSubArray = nestedArray[0];
    XCTAssertEqual(firstSubArray.count, 2, @"First sub-array should have valid elements only");
    XCTAssertEqualObjects(firstSubArray[0], @"nested1", @"First nested element should be preserved");
    XCTAssertEqualObjects(firstSubArray[1], @"nested2", @"Second nested element should be preserved");

    // Second sub-array should have 1 valid element
    NSArray *secondSubArray = nestedArray[1];
    XCTAssertEqual(secondSubArray.count, 1, @"Second sub-array should have valid elements only");
    XCTAssertEqualObjects(secondSubArray[0], @"nested3", @"Third nested element should be preserved");
}

/**
 * Test complex nested structures with mixed valid and nil values
 */
- (void)testComplexNestedStructures {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    // Create complex nested structure
    NSMutableDictionary *complexMeta = [NSMutableDictionary dictionary];
    complexMeta[@"validString"] = @"validValue";
    complexMeta[@"nilString"] = nil;
    complexMeta[@"nullString"] = [NSNull null];

    // Nested dictionary with arrays
    complexMeta[@"nestedDict"] = @{
        @"array1": @[@"valid1", @"valid2", [NSNull null]],
        // Use NSNull to represent nulls; method should filter NSNull-only arrays
        @"array2": @[[NSNull null], [NSNull null]],
        @"array3": @[@"onlyValid"],
        // Remove nil from literal; still exercises null filtering
        @"nestedDict2": @{
            @"deepValid": @"deepValue",
            @"deepNull": [NSNull null]
        }
    };

    // Array with dictionaries
    complexMeta[@"arrayWithDicts"] = @[
        @{@"valid": @"value1"},
        @{@"valid": @"value2", @"null": [NSNull null]},
        @{@"null": [NSNull null]}, // will be cleaned to empty and filtered
        @{@"valid": @"value3"}
    ];

    [self.knotapi addSafeMetaData:complexMeta forKey:@"complexMeta" toDictionary:dict];

    NSDictionary *resultMeta = dict[@"complexMeta"];
    XCTAssertNotNil(resultMeta, @"Complex metadata should be added");
    XCTAssertEqualObjects(resultMeta[@"validString"], @"validValue", @"Valid string should be preserved");
    XCTAssertNil(resultMeta[@"nilString"], @"Nil string should be filtered");
    XCTAssertNil(resultMeta[@"nullString"], @"NSNull string should be filtered");

    // Verify nested dictionary structure
    NSDictionary *nestedDict = resultMeta[@"nestedDict"];
    XCTAssertNotNil(nestedDict, @"Nested dictionary should be preserved");

    // Verify arrays are properly filtered
    NSArray *array1 = nestedDict[@"array1"];
    XCTAssertNotNil(array1, @"Array1 should be preserved");
    XCTAssertEqual(array1.count, 2, @"Array1 should have only valid values (nil and NSNull filtered out)");
    XCTAssertEqualObjects(array1[0], @"valid1", @"First valid array element should be preserved");
    XCTAssertEqualObjects(array1[1], @"valid2", @"Second valid array element should be preserved");

    NSArray *array2 = nestedDict[@"array2"];
    XCTAssertNil(array2, @"Array2 should be nil since it contains only nil/NSNull values");

    NSArray *array3 = nestedDict[@"array3"];
    XCTAssertNotNil(array3, @"Array3 should be preserved");
    XCTAssertEqual(array3.count, 1, @"Array3 should have only valid values");
    XCTAssertEqualObjects(array3[0], @"onlyValid", @"Valid array element should be preserved");
}

@end
