import APIClient
import ComposableArchitecture
import Foundation
import Utilities
import UIKit

/*
 Actor to manage play count batching for hook plays:
 - Max batch size is 10
 - Batch flush interval is 10 seconds
 - Batch is flushed when the max batch size is reached or the flush interval is reached
 - Batch is flushed when the app enters background (didEnterBackground)
 - Batch is flushed when the app is terminated (willTerminate)
 - Batch is flushed when duplicates are detected
 */
public actor HooksPlayCountManager {
    private var playCountBatch: [String: Int] = [:]
    private var batchFlushTimer: Task<Void, Error>?
    private let maxBatchSize = 10
    private let batchFlushInterval: TimeInterval = 10.0

    private var lifecycleObservers: [NSObjectProtocol] = []

    public init() {
        Task { await setupLifecycleObservers() }
    }

    deinit {
        Task { @MainActor [lifecycleObservers] in
            for observer in lifecycleObservers {
                NotificationCenter.default.removeObserver(observer)
            }
        }
    }

    /// Record a play count for the given hook immediately
    public func recordPlayCount(for hookId: String) {
        addToPlayCountBatch(hookId)
    }

    private func addToPlayCountBatch(_ hookId: String) {
        // Add to batch (increment count if already exists)
        playCountBatch[hookId, default: 0] += 1

        // Start flush timer if this is the first item
        if playCountBatch.count == 1 {
            startBatchFlushTimer()
        }

        // Flush immediately if we hit max batch size
        if playCountBatch.count >= maxBatchSize {
            Task {
                await flushPlayCountBatch()
            }
        }
    }

    private func startBatchFlushTimer() {
        // Cancel any existing timer
        batchFlushTimer?.cancel()

        // Start new timer
        batchFlushTimer = Task {
            do {
                try await Task.sleep(for: .seconds(batchFlushInterval))
                await flushPlayCountBatch()
            } catch {
                // Task was cancelled, do nothing
            }
        }
    }

    private func flushPlayCountBatch() async {
        guard !playCountBatch.isEmpty else { return }

        let batchToFlush = playCountBatch
        playCountBatch.removeAll()

        // Cancel the timer since we're flushing
        batchFlushTimer?.cancel()
        batchFlushTimer = nil

        // Use detached task to prevent cancellation
        // from parent task
        Task.detached { [weak self] in
            await self?.sendPlayCountBatch(batchToFlush)
        }
    }

    private func sendPlayCountBatch(_ hookIdsToCounts: [String: Int]) async {
        @Dependency(\.apiClientV2) var apiClient
        do {
            try await apiClient.incrementHookPlayCounts(hookIdsToCounts)
        } catch {
            log.telemetry.error(error, message: "Increment hook play counts request failed.")
        }
    }

    public func forceFlushPlayCountBatch() async {
        await flushPlayCountBatch()
    }

    /// Check if a hook ID would create a duplicate in the current batch
    public func wouldCreateDuplicate(_ hookId: String) -> Bool {
        return playCountBatch[hookId] != nil
    }

    /// Record a play count, flushing duplicates if necessary
    public func recordPlayCountIfNeeded(for hookId: String) async {
        // Check if this hook would create a duplicate in the batch
        if wouldCreateDuplicate(hookId) {
            // Flush the current batch before recording this play
            await forceFlushPlayCountBatch()
        }
        // Record the play count immediately (no delay)
        recordPlayCount(for: hookId)
    }

    private func setupLifecycleObservers() async {
        let willTerminateObserver = NotificationCenter.default.addObserver(
            forName: UIApplication.willTerminateNotification,
            object: nil,
            queue: nil
        ) { [weak self] _ in
            Task { [weak self] in
                await self?.flushPlayCountBatch()
            }
        }

        let didEnterBackgroundObserver = NotificationCenter.default.addObserver(
            forName: UIApplication.didEnterBackgroundNotification,
            object: nil,
            queue: nil
        ) { [weak self] _ in
            Task { [weak self] in
                await self?.flushPlayCountBatch()
            }
        }

        lifecycleObservers = [willTerminateObserver, didEnterBackgroundObserver]
    }
}

extension HooksPlayCountManager: DependencyKey {
    public static let liveValue = HooksPlayCountManager()
}

public extension DependencyValues {
    var hooksPlayCountManager: HooksPlayCountManager {
        get { self[HooksPlayCountManager.self] }
        set { self[HooksPlayCountManager.self] = newValue }
    }
}
