import Foundation

// MARK: - GCM Consent Types

/// The Google Consent Mode v2 consent type strings.
public enum GCMConsentType: String, CaseIterable, Sendable {
    case analyticsStorage     = "analytics_storage"
    case adStorage            = "ad_storage"
    case adUserData           = "ad_user_data"
    case adPersonalisation    = "ad_personalization"
    case functionalityStorage = "functionality_storage"
    case personalisationStorage = "personalization_storage"
    case securityStorage      = "security_storage"
}

/// The granted/denied status for a GCM consent type.
public enum GCMConsentStatus: String, Sendable {
    case granted = "granted"
    case denied  = "denied"
}

// MARK: - GCM Analytics Provider Protocol

/// Abstracts the Firebase Analytics / Google Tag Manager call surface.
///
/// Conforming types should call the underlying GCM API. The default no-op
/// implementation is used when Firebase is not present.
public protocol GCMAnalyticsProvider: AnyObject, Sendable {
    /// Sets the default consent state before any interaction.
    func setConsentDefaults(_ defaults: [String: String])

    /// Updates consent state after the user interacts with the banner.
    func updateConsent(_ updates: [String: String])
}

// MARK: - No-Op Provider

/// A no-op ``GCMAnalyticsProvider`` used when Firebase Analytics is not linked.
public final class NoOpGCMAnalyticsProvider: GCMAnalyticsProvider, @unchecked Sendable {
    public init() {}
    public func setConsentDefaults(_ defaults: [String: String]) {}
    public func updateConsent(_ updates: [String: String]) {}
}

// MARK: - GCM Bridge

/// Maps CMP consent state to Google Consent Mode v2 signals.
///
/// On app launch, call ``applyDefaults(config:)`` before the user interacts.
/// After consent is collected, call ``applyConsent(state:)`` to update GCM.
///
/// To integrate with Firebase Analytics, implement ``GCMAnalyticsProvider`` and
/// call the `Firebase.Analytics.setConsent(_:)` API inside it.
public final class GCMBridge: @unchecked Sendable {

    // MARK: - Dependencies

    private let provider: any GCMAnalyticsProvider

    // MARK: - Initialiser

    /// - Parameter provider: The analytics provider to forward consent signals to.
    ///                       Defaults to a no-op implementation.
    public init(provider: any GCMAnalyticsProvider = NoOpGCMAnalyticsProvider()) {
        self.provider = provider
    }

    // MARK: - Public API

    /// Sends default (pre-consent) consent signals to GCM based on the site's blocking mode.
    ///
    /// Call this as early as possible — ideally before any analytics events are sent.
    ///
    /// - Parameter config: The effective site configuration.
    public func applyDefaults(config: ConsentConfig) {
        let defaults = buildDefaults(for: config.blockingMode)
        provider.setConsentDefaults(defaults)
    }

    /// Updates GCM consent signals to reflect the user's explicit choices.
    ///
    /// - Parameter state: The resolved consent state after user interaction.
    public func applyConsent(state: ConsentState) {
        let updates = buildConsentMap(from: state)
        provider.updateConsent(updates)
    }

    // MARK: - Private Helpers

    /// Builds the default GCM consent map based on blocking mode.
    ///
    /// - `opt_in`: all types denied by default (GDPR).
    /// - `opt_out`: all types granted by default (CCPA).
    /// - `informational`: all types granted.
    private func buildDefaults(for mode: ConsentConfig.BlockingMode) -> [String: String] {
        let status: GCMConsentStatus = mode == .optIn ? .denied : .granted
        return Dictionary(
            uniqueKeysWithValues: GCMConsentType.allCases.map {
                ($0.rawValue, status.rawValue)
            }
        )
    }

    /// Maps the consent state's accepted/rejected categories to GCM consent type values.
    private func buildConsentMap(from state: ConsentState) -> [String: String] {
        var map: [String: String] = [:]

        // security_storage is always granted — it is necessary for security.
        map[GCMConsentType.securityStorage.rawValue] = GCMConsentStatus.granted.rawValue

        for category in ConsentCategory.allCases {
            guard let gcmType = category.gcmConsentType else { continue }
            let status: GCMConsentStatus = state.isGranted(category) ? .granted : .denied
            map[gcmType] = status.rawValue
        }

        // ad_user_data and ad_personalization follow the marketing category.
        let marketingGranted = state.isGranted(.marketing)
        map[GCMConsentType.adUserData.rawValue] = marketingGranted
            ? GCMConsentStatus.granted.rawValue
            : GCMConsentStatus.denied.rawValue
        map[GCMConsentType.adPersonalisation.rawValue] = marketingGranted
            ? GCMConsentStatus.granted.rawValue
            : GCMConsentStatus.denied.rawValue

        return map
    }
}
