@testable import AppStartUpClient
import Combine
import ConcurrencyExtras
import FirebaseClient
import Foundation
import Testing

@Suite("AppStartUpOrchestrator Tests")
struct AppStartUpOrchestratorTests {
    @Test("Start executes phases in correct order")
    func testStartExecutesPhasesInOrder() async throws {
        let (sut, mockSDKManager, mockMetricsCollector) = makeSUT(
            dependencies: [
                .firebase: SDKDependency(phase: .essential, timeout: 1.0),
                .braze: SDKDependency(phase: .critical, timeout: 1.0),
                .revenueCat: SDKDependency(phase: .important, timeout: 1.0),
                .adamantium: SDKDependency(phase: .optional, timeout: 1.0, isOptional: true),
            ]
        )

        var receivedEvents: [AppStartUpOrchestrationEvent] = []

        await sut.start { event in
            receivedEvents.append(event)
        }

        let expectedEvents: [AppStartUpOrchestrationEvent] = [
            .phaseStarted(.essential),
            .sdkInitialized(.firebase),
            .phaseStarted(.critical),
            .sdkInitialized(.braze),
            .canShowUI,
            .phaseStarted(.important),
            .sdkInitialized(.revenueCat),
            .phaseStarted(.optional),
            .sdkInitialized(.adamantium),
            .completed(.mock),
        ]

        #expect(receivedEvents == expectedEvents)
        #expect(mockMetricsCollector.startupBeganCalled)
        #expect(mockSDKManager.initializeCalls.contains(.firebase))
        #expect(mockSDKManager.initializeCalls.contains(.braze))
        #expect(mockSDKManager.initializeCalls.contains(.revenueCat))
        #expect(mockSDKManager.initializeCalls.contains(.adamantium))
    }

    @Test("Critical SDK failure triggers critical failure event")
    func testCriticalSDKFailure() async throws {
        let (sut, mockSDKManager, mockMetricsCollector) = makeSUT(
            dependencies: [
                .firebase: SDKDependency(phase: .essential, timeout: 1.0),
                .braze: SDKDependency(phase: .critical, timeout: 1.0, isOptional: false),
            ]
        )

        mockSDKManager.shouldFailSDKs = [.braze]

        var receivedEvents: [AppStartUpOrchestrationEvent] = []

        await sut.start { event in
            receivedEvents.append(event)
        }

        let hasCriticalFailure = receivedEvents.contains { event in
            if case .criticalFailure(let sdks) = event {
                return sdks.contains(.braze)
            }
            return false
        }

        #expect(hasCriticalFailure)
        #expect(mockMetricsCollector.sdkFailedCalls.contains(.braze))
    }

    @Test("Optional SDK failure is handled gracefully")
    func testOptionalSDKFailureHandling() async throws {
        let (sut, mockSDKManager, mockMetricsCollector) = makeSUT(
            dependencies: [
                .firebase: SDKDependency(phase: .essential, timeout: 1.0),
                .adamantium: SDKDependency(phase: .optional, timeout: 1.0, isOptional: true),
            ]
        )

        mockSDKManager.shouldFailSDKs = [.adamantium]

        var receivedEvents: [AppStartUpOrchestrationEvent] = []

        await sut.start { event in
            receivedEvents.append(event)
        }

        let hasCompletedEvent = receivedEvents.contains { event in
            if case .completed = event { return true }
            return false
        }

        let hasCriticalFailure = receivedEvents.contains { event in
            if case .criticalFailure = event { return true }
            return false
        }

        #expect(hasCompletedEvent)
        #expect(!hasCriticalFailure)
        #expect(mockMetricsCollector.sdkSkippedCalls.contains(.adamantium))
    }

    @Test("Dependency waiting works correctly")
    func testDependencyWaiting() async throws {
        let (sut, mockSDKManager, _) = makeSUT(
            dependencies: [
                .firebase: SDKDependency(phase: .essential, timeout: 2.0),
                .braze: SDKDependency(phase: .critical, requirements: [.firebase], timeout: 2.0),
            ]
        )

        mockSDKManager.initializeDelay = 0.1 // Add delay to firebase to test dependency waiting

        var receivedEvents: [AppStartUpOrchestrationEvent] = []

        await sut.start { event in
            receivedEvents.append(event)
        }

        let hasCompletedEvent = receivedEvents.contains { event in
            if case .completed = event { return true }
            return false
        }

        #expect(hasCompletedEvent)
        #expect(mockSDKManager.initializeCalls.contains(.firebase))
        #expect(mockSDKManager.initializeCalls.contains(.braze))
    }

    @Test("Timeout handling works correctly")
    func testTimeoutHandling() async throws {
        let (sut, mockSDKManager, mockMetricsCollector) = makeSUT(
            dependencies: [
                .firebase: SDKDependency(phase: .essential, timeout: 1.0),
                .braze: SDKDependency(phase: .critical, timeout: 0.1, isOptional: false),
            ]
        )

        mockSDKManager.shouldTimeoutSDKs = [.braze]

        var receivedEvents: [AppStartUpOrchestrationEvent] = []

        await sut.start { event in
            receivedEvents.append(event)
        }

        let hasCriticalFailure = receivedEvents.contains { event in
            if case .criticalFailure(let sdks) = event {
                return sdks.contains(.braze)
            }
            return false
        }

        #expect(hasCriticalFailure)
        #expect(mockMetricsCollector.sdkFailedCalls.contains(.braze))
    }

    @Test("Metrics collection works correctly")
    func testMetricsCollection() async throws {
        let (sut, _, mockMetricsCollector) = makeSUT(
            dependencies: [
                .firebase: SDKDependency(phase: .essential, timeout: 1.0),
            ]
        )

        var receivedEvents: [AppStartUpOrchestrationEvent] = []

        await sut.start { event in
            receivedEvents.append(event)
        }

        #expect(mockMetricsCollector.startupBeganCalled)
        #expect(mockMetricsCollector.sdkStartedCalls.contains(.firebase))
        #expect(mockMetricsCollector.sdkCompletedCalls.contains(.firebase))
        #expect(mockMetricsCollector.getMetricsCalled)
    }

    @Test("Firebase spans are created when Firebase is ready")
    func testFirebaseSpansCreatedWhenReady() async throws {
        var traceCalled = false
        var firebaseClient = FirebaseClient.performanceIsReady
        firebaseClient.trace = { _ in
            traceCalled = true
            return AnyCancellable {}
        }

        let (sut, _, _) = makeSUT(
            dependencies: [
                .firebase: SDKDependency(phase: .essential, timeout: 1.0),
            ],
            firebaseClient: firebaseClient
        )

        var receivedEvents: [AppStartUpOrchestrationEvent] = []

        await sut.start { event in
            receivedEvents.append(event)
        }

        let hasCompletedEvent = receivedEvents.contains { event in
            if case .completed = event { return true }
            return false
        }

        #expect(traceCalled)
        #expect(hasCompletedEvent)
    }

    @Test("Firebase spans are not created when Firebase is not ready")
    func testFirebaseSpansNotReadyWhenPerformanceNotReady() async throws {
        var firebaseClient = FirebaseClient.performanceNotReady
        firebaseClient.trace = { _ in
            Issue.record("Unexpected trace call")
            return AnyCancellable {}
        }

        let (sut, _, _) = makeSUT(
            dependencies: [
                .firebase: SDKDependency(phase: .essential, timeout: 1.0),
            ],
            firebaseClient: firebaseClient
        )

        var receivedEvents: [AppStartUpOrchestrationEvent] = []

        await sut.start { event in
            receivedEvents.append(event)
        }

        let hasCompletedEvent = receivedEvents.contains { event in
            if case .completed = event { return true }
            return false
        }
        #expect(hasCompletedEvent)
    }

    // MARK: - Helper Methods

    private func makeSUT(
        dependencies: [SDKIdentifier: SDKDependency] = [:],
        firebaseClient: FirebaseClient = .testValue,
        now: @escaping () -> Date = { Date() }
    ) -> (AppStartUpOrchestrator, DependencySDKManager.Mock, DefaultMetricsCollector.Mock) {
        let mockSDKManager = DependencySDKManager.Mock()
        let mockMetricsCollector = DefaultMetricsCollector.Mock()

        let initialStates: [SDKIdentifier: SDKState] = Dictionary(
            uniqueKeysWithValues: dependencies.keys.map { ($0, .notStarted) }
        )

        let orchestrator = AppStartUpOrchestrator(
            sdkManager: mockSDKManager,
            metricsCollector: mockMetricsCollector,
            dependencies: dependencies,
            sdkStates: initialStates,
            firebaseClient: firebaseClient,
            now: now
        )

        return (orchestrator, mockSDKManager, mockMetricsCollector)
    }
}
