import ConcurrencyExtras
import Foundation

// MARK: - MetricsCollector

protocol MetricsCollectorProtocol {
    func startupBegan(at date: Date)
    func sdkStarted(_ sdk: SDKIdentifier, at date: Date)
    func sdkCompleted(_ sdk: SDKIdentifier, duration: TimeInterval, success: Bool)
    func sdkFailed(_ sdk: SDKIdentifier, error: Error, duration: TimeInterval)
    func sdkSkipped(_ sdk: SDKIdentifier, reason: String)
    func getMetrics(totalDuration: TimeInterval) -> AppStartUpMetrics
}

// MARK: - DefaultMetricsCollector

final class DefaultMetricsCollector: MetricsCollectorProtocol {
    struct SDKMetricData {
        let startTime: Date
        var endTime: Date?
        var success: Bool = false
        var error: String?
        var skipped: Bool = false
        var skipReason: String?
    }

    private struct State {
        var startTime: Date?
        var sdkMetrics: [SDKIdentifier: SDKMetricData]
    }

    private let state: LockIsolated<State>

    init(
        startTime: Date? = nil,
        sdkMetrics: [SDKIdentifier: SDKMetricData] = [:]
    ) {
        self.state = .init(
            State(
                startTime: startTime,
                sdkMetrics: sdkMetrics
            )
        )
    }

    public func startupBegan(
        at date: Date
    ) {
        state.withValue { $0.startTime = date }
    }

    public func sdkStarted(
        _ sdk: SDKIdentifier,
        at date: Date
    ) {
        state.withValue { $0.sdkMetrics[sdk] = SDKMetricData(startTime: date) }
    }

    public func sdkCompleted(_ sdk: SDKIdentifier, duration _: TimeInterval, success _: Bool) {
        state.withValue { state in
            state.sdkMetrics[sdk]?.endTime = Date()
            state.sdkMetrics[sdk]?.success = true
        }
    }

    public func sdkFailed(
        _ sdk: SDKIdentifier,
        error: Error,
        duration _: TimeInterval
    ) {
        state.withValue { state in
            state.sdkMetrics[sdk]?.endTime = Date()
            state.sdkMetrics[sdk]?.success = false
            state.sdkMetrics[sdk]?.error = error.localizedDescription
        }
    }

    public func sdkSkipped(
        _ sdk: SDKIdentifier,
        reason: String
    ) {
        state.withValue { state in
            state.sdkMetrics[sdk]?.skipped = true
            state.sdkMetrics[sdk]?.skipReason = reason
        }
    }

    public func getMetrics(
        totalDuration: TimeInterval
    ) -> AppStartUpMetrics {
        state.withValue { state in
            var metrics = AppStartUpMetrics()
            metrics.startTime = state.startTime
            metrics.totalDuration = totalDuration

            for (sdk, data) in state.sdkMetrics {
                let metric = AppStartUpMetrics.SDKMetric(
                    identifier: sdk,
                    startTime: data.startTime,
                    endTime: data.endTime,
                    success: data.success,
                    retryCount: 0,
                    threadName: Thread.current.name ?? "unknown"
                )
                metrics.sdkMetrics[sdk] = metric
            }

            return metrics
        }
    }
}

#if DEBUG

    // MARK: - Mock

    extension DefaultMetricsCollector {
        final class Mock: MetricsCollectorProtocol {
            var startupBeganCalled = false
            var sdkStartedCalls: [SDKIdentifier] = []
            var sdkCompletedCalls: [SDKIdentifier] = []
            var sdkFailedCalls: [SDKIdentifier] = []
            var sdkSkippedCalls: [SDKIdentifier] = []
            var getMetricsCalled = false

            func startupBegan(at _: Date) {
                startupBeganCalled = true
            }

            func sdkStarted(_ sdk: SDKIdentifier, at _: Date) {
                sdkStartedCalls.append(sdk)
            }

            func sdkCompleted(_ sdk: SDKIdentifier, duration _: TimeInterval, success _: Bool) {
                sdkCompletedCalls.append(sdk)
            }

            func sdkFailed(_ sdk: SDKIdentifier, error _: Error, duration _: TimeInterval) {
                sdkFailedCalls.append(sdk)
            }

            func sdkSkipped(_ sdk: SDKIdentifier, reason _: String) {
                sdkSkippedCalls.append(sdk)
            }

            func getMetrics(totalDuration _: TimeInterval) -> AppStartUpMetrics {
                getMetricsCalled = true
                return .mock
            }
        }
    }
#endif
