@testable import AppStartUpClient
import Foundation
import Testing

@Suite("MetricsCollector Tests")
struct MetricsCollectorTests {
    // MARK: - Initialization Tests

    @Test("DefaultMetricsCollector initializes with empty state")
    func testInitializesWithEmptyState() {
        let sut = DefaultMetricsCollector()
        let metrics = sut.getMetrics(totalDuration: 0)

        #expect(metrics.startTime == nil)
        #expect(metrics.sdkMetrics.isEmpty)
        #expect(metrics.totalDuration == 0)
    }

    @Test("DefaultMetricsCollector initializes with provided state")
    func testInitializesWithProvidedState() {
        let startTime = Date()
        let sdkMetrics: [SDKIdentifier: DefaultMetricsCollector.SDKMetricData] = [
            .firebase: DefaultMetricsCollector.SDKMetricData(startTime: startTime),
        ]

        let sut = DefaultMetricsCollector(startTime: startTime, sdkMetrics: sdkMetrics)
        let metrics = sut.getMetrics(totalDuration: 5.0)

        #expect(metrics.startTime == startTime)
        #expect(metrics.totalDuration == 5.0)
        #expect(metrics.sdkMetrics.count == 1)
        #expect(metrics.sdkMetrics[.firebase]?.identifier == .firebase)
    }

    // MARK: - Startup Tracking Tests

    @Test("startupBegan sets start time")
    func testStartupBeganSetsStartTime() {
        let sut = DefaultMetricsCollector()
        let startTime = Date()

        sut.startupBegan(at: startTime)
        let metrics = sut.getMetrics(totalDuration: 1.0)

        #expect(metrics.startTime == startTime)
    }

    @Test("startupBegan updates existing start time")
    func testStartupBeganUpdatesExistingStartTime() {
        let originalStartTime = Date()
        let sut = DefaultMetricsCollector(startTime: originalStartTime)
        let newStartTime = Date().addingTimeInterval(10)

        sut.startupBegan(at: newStartTime)
        let metrics = sut.getMetrics(totalDuration: 1.0)

        #expect(metrics.startTime == newStartTime)
        #expect(metrics.startTime != originalStartTime)
    }

    // MARK: - SDK Lifecycle Tests

    @Test("sdkStarted creates new SDK metric entry")
    func testSdkStartedCreatesNewEntry() {
        let sut = DefaultMetricsCollector()
        let startTime = Date()

        sut.sdkStarted(.firebase, at: startTime)
        let metrics = sut.getMetrics(totalDuration: 1.0)

        #expect(metrics.sdkMetrics.count == 1)
        let firebaseMetric = metrics.sdkMetrics[.firebase]
        #expect(firebaseMetric?.identifier == .firebase)
        #expect(firebaseMetric?.startTime == startTime)
        #expect(firebaseMetric?.endTime == nil)
        #expect(firebaseMetric?.success == false)
    }

    @Test("sdkStarted replaces existing SDK metric entry")
    func testSdkStartedReplacesExistingEntry() {
        let originalStartTime = Date()
        let originalSDKMetrics: [SDKIdentifier: DefaultMetricsCollector.SDKMetricData] = [
            .firebase: DefaultMetricsCollector.SDKMetricData(startTime: originalStartTime),
        ]
        let sut = DefaultMetricsCollector(sdkMetrics: originalSDKMetrics)
        let newStartTime = Date().addingTimeInterval(10)

        sut.sdkStarted(.firebase, at: newStartTime)
        let metrics = sut.getMetrics(totalDuration: 1.0)

        #expect(metrics.sdkMetrics.count == 1)
        let firebaseMetric = metrics.sdkMetrics[.firebase]
        #expect(firebaseMetric?.startTime == newStartTime)
        #expect(firebaseMetric?.startTime != originalStartTime)
    }

    @Test("sdkCompleted marks SDK as successful")
    func testSdkCompletedMarksAsSuccessful() {
        let sut = DefaultMetricsCollector()
        let startTime = Date()

        sut.sdkStarted(.braze, at: startTime)
        sut.sdkCompleted(.braze, duration: 2.0, success: true)

        let metrics = sut.getMetrics(totalDuration: 5.0)
        let brazeMetric = metrics.sdkMetrics[.braze]

        #expect(brazeMetric?.success == true)
        #expect(brazeMetric?.endTime != nil)
        #expect((brazeMetric?.endTime)! >= startTime)
    }

    @Test("sdkFailed marks SDK as failed with error")
    func testSdkFailedMarksAsFailedWithError() {
        let sut = DefaultMetricsCollector()
        let startTime = Date()
        let error = NSError(
            domain: "TestError",
            code: 500,
            userInfo: [NSLocalizedDescriptionKey: "Test error message"]
        )

        sut.sdkStarted(.clerk, at: startTime)
        sut.sdkFailed(.clerk, error: error, duration: 1.5)

        let metrics = sut.getMetrics(totalDuration: 5.0)
        let clerkMetric = metrics.sdkMetrics[.clerk]

        #expect(clerkMetric?.success == false)
        #expect(clerkMetric?.endTime != nil)
        #expect((clerkMetric?.endTime)! >= startTime)
    }

    @Test("sdkSkipped marks SDK as skipped with reason")
    func testSdkSkippedMarksAsSkippedWithReason() {
        let sut = DefaultMetricsCollector()
        let startTime = Date()
        let skipReason = "Not available in test environment"

        sut.sdkStarted(.revenueCat, at: startTime)
        sut.sdkSkipped(.revenueCat, reason: skipReason)

        let metrics = sut.getMetrics(totalDuration: 3.0)

        #expect(metrics.sdkMetrics.count == 1)
        #expect(metrics.sdkMetrics[.revenueCat]?.identifier == .revenueCat)
    }

    // MARK: - Complex Flow Tests

    @Test("handles multiple SDKs with different outcomes")
    func testHandlesMultipleSDKsWithDifferentOutcomes() {
        let sut = DefaultMetricsCollector()
        let startTime = Date()
        let error = NSError(
            domain: "TestError",
            code: 404,
            userInfo: [NSLocalizedDescriptionKey: "Not found"]
        )

        // Start tracking overall startup
        sut.startupBegan(at: startTime)

        // Firebase completes successfully
        sut.sdkStarted(.firebase, at: startTime)
        sut.sdkCompleted(.firebase, duration: 1.0, success: true)

        // Braze fails
        sut.sdkStarted(.braze, at: startTime.addingTimeInterval(1))
        sut.sdkFailed(.braze, error: error, duration: 0.5)

        // Clerk is skipped
        sut.sdkStarted(.clerk, at: startTime.addingTimeInterval(2))
        sut.sdkSkipped(.clerk, reason: "Disabled in test")

        let metrics = sut.getMetrics(totalDuration: 10.0)

        #expect(metrics.startTime == startTime)
        #expect(metrics.totalDuration == 10.0)
        #expect(metrics.sdkMetrics.count == 3)

        // Verify Firebase
        let firebaseMetric = metrics.sdkMetrics[.firebase]
        #expect(firebaseMetric?.success == true)
        #expect(firebaseMetric?.endTime != nil)

        // Verify Braze
        let brazeMetric = metrics.sdkMetrics[.braze]
        #expect(brazeMetric?.success == false)
        #expect(brazeMetric?.endTime != nil)

        // Verify Clerk
        let clerkMetric = metrics.sdkMetrics[.clerk]
        #expect(clerkMetric != nil)
    }

    @Test("getMetrics sets retry count to zero")
    func testGetMetricsSetsRetryCountToZero() {
        let sut = DefaultMetricsCollector()
        let startTime = Date()

        sut.sdkStarted(.adamantium, at: startTime)
        sut.sdkCompleted(.adamantium, duration: 0.5, success: true)

        let metrics = sut.getMetrics(totalDuration: 1.0)
        let adamantiumMetric = metrics.sdkMetrics[.adamantium]

        #expect(adamantiumMetric?.retryCount == 0)
    }

    // MARK: - Edge Cases

    @Test("handles operations on non-existent SDK")
    func testHandlesOperationsOnNonExistentSDK() {
        let sut = DefaultMetricsCollector()
        let error = NSError(
            domain: "TestError",
            code: 400,
            userInfo: [NSLocalizedDescriptionKey: "Bad request"]
        )

        // Try to complete an SDK that was never started
        sut.sdkCompleted(.shareAsset, duration: 1.0, success: true)
        sut.sdkFailed(.attribution, error: error, duration: 0.5)
        sut.sdkSkipped(.rageshake, reason: "Not needed")

        let metrics = sut.getMetrics(totalDuration: 2.0)

        // Should not crash and should return empty metrics
        #expect(metrics.sdkMetrics.isEmpty)
    }

    @Test("thread safety with concurrent access")
    func testThreadSafetyWithConcurrentAccess() async {
        let sut = DefaultMetricsCollector()
        let startTime = Date()

        // Simulate concurrent operations
        await withTaskGroup(of: Void.self) { group in
            group.addTask {
                sut.startupBegan(at: startTime)
            }

            group.addTask {
                sut.sdkStarted(.firebase, at: startTime)
                sut.sdkCompleted(.firebase, duration: 1.0, success: true)
            }

            group.addTask {
                sut.sdkStarted(.braze, at: startTime)
                sut.sdkCompleted(.braze, duration: 1.5, success: true)
            }

            group.addTask {
                _ = sut.getMetrics(totalDuration: 3.0)
            }
        }

        let finalMetrics = sut.getMetrics(totalDuration: 3.0)

        // Should not crash and should have consistent state
        #expect(finalMetrics.startTime == startTime)
        #expect(finalMetrics.totalDuration == 3.0)
    }
}
