//
// Copyright 2025 Element Creations Ltd.
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//

@testable import ElementX
import Observation
import Testing

@MainActor
@Suite
struct DeferredFulfillmentTests {
    private let observable = SomeObservable()
    
    @Test
    func observableWithoutUpdate() async throws {
        // Given a deferred fulfilment on a value that already matches the expected value.
        let initialValue = observable.counter
        let deferred = deferFulfillment(observable.observe(\.counter)) { $0 == initialValue }
        
        // Then the test should be fulfilled by the initial value and shouldn't timeout whilst waiting for a update.
        try await deferred.fulfill()
    }
    
    @Test
    func observableWithSynchronousUpdate() async throws {
        // Given a deferred fulfilment for an expected value.
        let newValue = 100
        let deferred = deferFulfillment(observable.observe(\.counter)) { $0 == newValue }
        
        // When that value is changed synchronously.
        observable.counter = newValue
        #expect(observable.counter == newValue)
        
        // Then the test should be fulfilled.
        try await deferred.fulfill()
        #expect(observable.counter == newValue)
    }
    
    @Test
    func observableAsynchronousUpdate() async throws {
        // Given a deferred fulfilment for an expected value.
        let newValue = 100
        let deferred = deferFulfillment(observable.observe(\.counter)) { $0 == newValue }
        
        // When that value is changed asynchronously.
        Task { try await observable.setCounter(newValue, delay: .seconds(1)) }
        #expect(observable.counter == 0)
        
        // Then the test should be fulfilled once the update has taken place.
        try await deferred.fulfill()
        #expect(observable.counter == newValue)
    }
    
    @Test
    func observableMultipleUpdates() async throws {
        // Given a deferred fulfilment for an expected value.
        let finalValue = 500
        let deferred = deferFulfillment(observable.observe(\.counter)) { $0 == finalValue }
        
        // When that value is changed asynchronously with some intermediate values before it is reached.
        Task {
            try await observable.setCounter(100, delay: .seconds(.random(in: 1.0...2.0)))
            try await observable.setCounter(250, delay: .seconds(.random(in: 1.0...2.0)))
            try await observable.setCounter(finalValue, delay: .seconds(.random(in: 1.0...2.0)))
        }
        #expect(observable.counter == 0)
        
        // Then the test should be fulfilled once the expected update has taken place.
        try await deferred.fulfill()
        #expect(observable.counter == finalValue)
    }
}

// MARK: - Helpers

@Observable
@MainActor private class SomeObservable {
    var counter = 0
    
    func setCounter(_ newValue: Int, delay: Duration? = nil) async throws {
        if let delay {
            try await Task.sleep(for: delay)
        }
        counter = newValue
    }
}
