#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import "Knotapi.h"
#import "KnotapiTestConfiguration.h"
#import "KnotAPI/KnotAPI-Swift.h"

@interface KnotapiCrashScenarioTests : XCTestCase
@property (nonatomic, strong) Knotapi *knotapi;
@property (nonatomic, strong) id mockKnot;
@end

@implementation KnotapiCrashScenarioTests

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

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

#pragma mark - Crash Scenario Tests

- (void)testBridgeTeardownRaceCondition {
    // This test simulates the exact crash scenario from the Klarna crash report
    // where events are sent to a deallocated bridge
    
    // Given: Bridge is active and receiving events
    XCTAssertTrue(self.knotapi.isBridgeActive);
    
    // When: Bridge is marked as inactive (simulating deallocation)
    self.knotapi.isBridgeActive = NO;
    
    // And: Events are still being sent (simulating async events after teardown)
    KnotEvent *mockEvent = OCMClassMock([KnotEvent class]);
    OCMStub([mockEvent environment]).andReturnValue(@(EnvironmentSandbox));
    OCMStub([mockEvent event]).andReturn(@"test_event");
    OCMStub([mockEvent merchant]).andReturn(@"test_merchant");
    OCMStub([mockEvent merchantId]).andReturn(@"123");
    OCMStub([mockEvent product]).andReturnValue(@(0));
    OCMStub([mockEvent taskId]).andReturn(@"task_123");
    OCMStub([mockEvent metaData]).andReturn(@{});
    
    // Then: Events should be safely ignored (no crash)
    XCTAssertNoThrow([self.knotapi onEventWithEvent:mockEvent], @"Event should be safely ignored when bridge is inactive");
    XCTAssertNoThrow([self.knotapi onErrorWithError:KnotErrorInvalidSession], @"Error should be safely ignored when bridge is inactive");
    XCTAssertNoThrow([self.knotapi onSuccessWithMerchant:@"test_merchant"], @"Success should be safely ignored when bridge is inactive");
    XCTAssertNoThrow([self.knotapi onExit], @"Exit should be safely ignored when bridge is inactive");
}

- (void)testMultipleCloseCallsRaceCondition {
    // This test simulates rapid close() calls that could cause double teardown
    
    // Given: Bridge is active
    XCTAssertTrue(self.knotapi.isBridgeActive);
    XCTAssertFalse(self.knotapi.isClosing);
    
    // When: Multiple close calls happen rapidly
    [self.knotapi close];
    [self.knotapi close];
    [self.knotapi close];
    [self.knotapi close];
    
    // Then: Only first call should be processed
    XCTAssertTrue(self.knotapi.isClosing, @"Closing flag should be set");
    
    // Wait for async completion
    XCTestExpectation *expectation = [self expectationWithDescription:@"Close completes"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [expectation fulfill];
    });
    [self waitForExpectationsWithTimeout:1.0 handler:nil];
    
    // Bridge should be inactive
    XCTAssertFalse(self.knotapi.isBridgeActive, @"Bridge should be inactive after close");
}

- (void)testEventAfterCloseRaceCondition {
    // This test simulates events arriving after close() is called but before cleanup completes
    
    // Given: Bridge is active
    XCTAssertTrue(self.knotapi.isBridgeActive);
    
    // When: Close is called
    [self.knotapi close];
    
    // And: Events arrive immediately after close (before async cleanup)
    KnotEvent *mockEvent = OCMClassMock([KnotEvent class]);
    OCMStub([mockEvent environment]).andReturnValue(@(EnvironmentSandbox));
    OCMStub([mockEvent event]).andReturn(@"test_event");
    OCMStub([mockEvent merchant]).andReturn(@"test_merchant");
    OCMStub([mockEvent merchantId]).andReturn(@"123");
    OCMStub([mockEvent product]).andReturnValue(@(0));
    OCMStub([mockEvent taskId]).andReturn(@"task_123");
    OCMStub([mockEvent metaData]).andReturn(@{});
    
    // Then: Events should be safely ignored (no crash)
    XCTAssertNoThrow([self.knotapi onEventWithEvent:mockEvent], @"Event should be safely ignored after close");
    XCTAssertNoThrow([self.knotapi onErrorWithError:KnotErrorInvalidSession], @"Error should be safely ignored after close");
    XCTAssertNoThrow([self.knotapi onSuccessWithMerchant:@"test_merchant"], @"Success should be safely ignored after close");
    XCTAssertNoThrow([self.knotapi onExit], @"Exit should be safely ignored after close");
}

- (void)testOpenCloseOpenRaceCondition {
    // This test simulates rapid open/close/open cycles
    
    // Given: Initial state
    XCTAssertTrue(self.knotapi.isBridgeActive);
    
    // When: Opening
    NSDictionary *params = [KnotapiTestConfiguration validTestParams];
    OCMStub([self.mockKnot openWithConfiguration:[OCMArg any] delegate:[OCMArg any]]);
    [self.knotapi open:params];
    
    // Immediately close (before open completes)
    [self.knotapi close];
    
    // Immediately open again (before close completes)
    [self.knotapi open:params];
    
    // Wait for all async operations
    XCTestExpectation *expectation = [self expectationWithDescription:@"All operations complete"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [expectation fulfill];
    });
    [self waitForExpectationsWithTimeout:1.0 handler:nil];
    
    // Then: Bridge should be in a consistent state
    XCTAssertTrue(self.knotapi.isBridgeActive, @"Bridge should be active after final open");
    XCTAssertFalse(self.knotapi.isClosing, @"Bridge should not be closing after final open");
}

- (void)testExceptionDuringClose {
    // This test simulates exceptions during close() that could cause crashes
    
    // Given: Knot.close throws an exception
    OCMStub([self.mockKnot close]).andThrow([NSException exceptionWithName:@"TestException" reason:@"Test" userInfo:nil]);
    
    // When: Closing
    // Then: Should not crash
    XCTAssertNoThrow([self.knotapi close], @"Close should handle exceptions gracefully");
    
    // Wait for async completion
    XCTestExpectation *expectation = [self expectationWithDescription:@"Close with exception completes"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [expectation fulfill];
    });
    [self waitForExpectationsWithTimeout:1.0 handler:nil];
    
    // Bridge should still be marked as inactive
    XCTAssertFalse(self.knotapi.isBridgeActive, @"Bridge should be inactive even after exception");
}

- (void)testMemoryPressureScenario {
    // This test simulates memory pressure scenarios that could cause crashes
    
    // Given: Bridge is active
    XCTAssertTrue(self.knotapi.isBridgeActive);
    
    // When: Simulating memory pressure by rapidly creating and destroying objects
    for (int i = 0; i < 100; i++) {
        Knotapi *tempBridge = [[Knotapi alloc] init];
        tempBridge.isBridgeActive = NO; // Simulate deallocation
        tempBridge = nil;
    }
    
    // And: Events are still being sent to the original bridge
    KnotEvent *mockEvent = OCMClassMock([KnotEvent class]);
    OCMStub([mockEvent environment]).andReturnValue(@(EnvironmentSandbox));
    OCMStub([mockEvent event]).andReturn(@"test_event");
    OCMStub([mockEvent merchant]).andReturn(@"test_merchant");
    OCMStub([mockEvent merchantId]).andReturn(@"123");
    OCMStub([mockEvent product]).andReturnValue(@(0));
    OCMStub([mockEvent taskId]).andReturn(@"task_123");
    OCMStub([mockEvent metaData]).andReturn(@{});
    
    // Then: Events should be safely ignored (no crash)
    XCTAssertNoThrow([self.knotapi onEventWithEvent:mockEvent], @"Event should be safely ignored during memory pressure");
}

- (void)testConcurrentAccessScenario {
    // This test simulates concurrent access to bridge methods
    
    // Given: Bridge is active
    XCTAssertTrue(self.knotapi.isBridgeActive);
    
    // When: Multiple threads access the bridge simultaneously
    dispatch_group_t group = dispatch_group_create();
    
    // Thread 1: Send events
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i < 10; i++) {
            KnotEvent *mockEvent = OCMClassMock([KnotEvent class]);
            OCMStub([mockEvent environment]).andReturnValue(@(EnvironmentSandbox));
            OCMStub([mockEvent event]).andReturn(@"test_event");
            OCMStub([mockEvent merchant]).andReturn(@"test_merchant");
            OCMStub([mockEvent merchantId]).andReturn(@"123");
            OCMStub([mockEvent product]).andReturnValue(@(0));
            OCMStub([mockEvent taskId]).andReturn(@"task_123");
            OCMStub([mockEvent metaData]).andReturn(@{});
            
            [self.knotapi onEventWithEvent:mockEvent];
        }
    });
    
    // Thread 2: Close bridge
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self.knotapi close];
    });
    
    // Thread 3: Send more events
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i < 10; i++) {
            [self.knotapi onErrorWithError:KnotErrorInvalidSession];
        }
    });
    
    // Wait for all threads to complete
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    // Then: No crash should occur
    XCTAssertTrue(self.knotapi.isClosing, @"Bridge should be closing");
}

@end
