import Foundation
import GoogleMobileAds

/**
 * BigCrunch environment configuration
 */
public enum BigCrunchEnv {
    case prod
    case staging
}

/**
 * Callback protocol for SDK initialization completion
 */
public protocol BigCrunchInitializationCallback: AnyObject {
    /**
     * Called when SDK initialization is complete and config is ready
     */
    func onInitialized()

    /**
     * Called if SDK initialization fails
     * - Parameter error: Error message describing the failure
     */
    func onInitializationFailed(error: String)
}

/**
 * BigCrunch Mobile Ads SDK - Main entry point
 *
 * Initialize the SDK before using any ad components:
 * ```swift
 * BigCrunchAds.initialize(
 *     propertyId: "your-property-id"
 * )
 * ```
 */
public final class BigCrunchAds {

    private static var _isInitialized = false
    private static var configManager: ConfigManager!
    private static var analyticsClient: AnalyticsClient!
    private static var bidRequestClient: BidRequestClient?
    internal static var privacyStore = PrivacyStore()

    /// Check if the SDK has been initialized (internal use)
    internal static func isInitialized() -> Bool {
        return _isInitialized
    }

    internal static var propertyId: String = ""
    internal static var environment: BigCrunchEnv = .prod

    // State for debug and test device management
    private static var debugMode = false
    private static var testDeviceIds: [String] = []

    /**
     * Initialize the BigCrunch Ads SDK
     *
     * - Parameters:
     *   - propertyId: Your BigCrunch property ID
     *   - env: Environment (prod or staging)
     *   - useMockConfig: If true, uses hardcoded mock config for testing (default: false)
     *   - useTestAds: If true, uses Google's test ad units instead of production units (default: false)
     *   - callback: Optional callback to be notified when initialization is complete
     */
    public static func initialize(
        propertyId: String,
        env: BigCrunchEnv = .prod,
        useMockConfig: Bool = false,
        useTestAds: Bool = false,
        callback: BigCrunchInitializationCallback? = nil
    ) {
        if _isInitialized {
            BCLogger.warning("SDK already initialized, ignoring duplicate call")
            return
        }

        BCLogger.info("BigCrunch Ads SDK v\(DeviceContext.SDK_VERSION) initializing...")
        BCLogger.debug("Property ID: \(propertyId), Environment: \(env)")

        // Store initialization parameters
        self.propertyId = propertyId
        self.environment = env

        // Initialize Google Mobile Ads SDK
        // Note: Google Mobile Ads SDK should be initialized by the app before calling BigCrunchAds.initialize()
        BCLogger.debug("Google Mobile Ads SDK should be initialized by the app")

        // Enable verbose logging in staging
        if env == .staging {
            BCLogger.isEnabled = true
        }

        // Create internal components
        let httpClient = HTTPClient()
        let storage = UserDefaultsStore()

        // Analytics always uses pipeline.bigcrunch.com for both prod and staging
        let baseURL = "https://pipeline.bigcrunch.com"

        configManager = ConfigManager(httpClient: httpClient, storage: storage)
        if useTestAds {
            configManager.useTestAdsOverride = true
        }
        analyticsClient = AnalyticsClient(httpClient: httpClient, baseURL: baseURL)

        // Mark as initialized before async config load
        _isInitialized = true

        // Kick off async config fetch.
        // Capture a strong reference for the detached task: the static is implicitly
        // unwrapped and can be nilled (e.g. by resetForTesting) before the task runs.
        BCLogger.debug("Fetching app configuration...")
        let loadingConfigManager: ConfigManager = configManager
        Task {
            let result = await loadingConfigManager.loadConfig(
                propertyId: propertyId,
                isProd: env == .prod,
                useMockConfig: useMockConfig
            )

            switch result {
            case .success(let config):
                BCLogger.debug("Configuration loaded successfully")
                BCLogger.verbose("Placements: \(config.placements.count)")

                // Create shared BidRequestClient for S2S demand
                if config.s2s.enabled {
                    bidRequestClient = BidRequestClient(
                        httpClient: httpClient,
                        configManager: loadingConfigManager,
                        privacyStore: privacyStore,
                        s2sConfig: config.s2s
                    )
                    BCLogger.debug("BidRequestClient created: \(config.s2s.serverUrl)")
                } else {
                    BCLogger.debug("S2S is disabled in config")
                }

                // Notify callback of successful initialization
                await MainActor.run {
                    callback?.onInitialized()
                }
            case .failure(let error):
                BCLogger.error("Failed to load configuration: \(error)")

                // Notify callback of initialization failure
                await MainActor.run {
                    callback?.onInitializationFailed(error: error.localizedDescription)
                }
            }
        }

        BCLogger.debug("BigCrunch Ads SDK initialization complete")
    }

    /**
     * Track screen view for analytics
     *
     * - Parameters:
     *   - screenName: Name of the screen being viewed
     *   - options: Optional overrides for page URL, content metadata, and custom dimensions
     */
    public static func trackScreen(_ screenName: String, options: ScreenViewOptions? = nil) {
        guard _isInitialized else {
            BCLogger.warning("trackScreen called before initialization, ignoring")
            return
        }
        analyticsClient.trackScreenView(screenName, options: options)
        SessionManager.shared.startPageView()
    }

    // MARK: - Configuration

    /**
     * Get the current app configuration
     *
     * - Returns: The app configuration, or nil if not yet loaded
     */
    public static func getAppConfig() -> AppConfig? {
        guard _isInitialized else { return nil }
        return configManager.getCachedConfig()
    }

    /**
     * Refresh the app configuration from the server
     */
    public static func refreshConfig() async {
        guard _isInitialized else {
            BCLogger.warning("refreshConfig called before initialization, ignoring")
            return
        }
        _ = await configManager.loadConfig(
            propertyId: propertyId,
            isProd: environment == .prod,
            useMockConfig: false
        )
    }

    // MARK: - Session Management

    /**
     * Get current session information
     *
     * - Returns: Session info with tracking counts
     */
    public static func getSessionInfo() -> SessionInfo {
        let sm = SessionManager.shared
        return SessionInfo(
            sessionId: sm.sessionId,
            userId: sm.userId,
            startTime: sm.sessionStartTime,
            isNewUser: sm.isNewUser,
            sessionDepth: sm.sessionDepth,
            screenViewCount: sm.sessionDepth,
            adRequestCount: sm.adRequestCount,
            adImpressionCount: sm.adImpressionCount,
            totalRevenueMicros: sm.totalRevenueMicros
        )
    }

    // MARK: - UTM Attribution

    /**
     * Set UTM attribution parameters for the current and future sessions
     *
     * Parameters are persisted and included in all analytics events until cleared.
     * Typically set from deep link parameters.
     */
    public static func setUTMParameters(
        source: String? = nil,
        medium: String? = nil,
        campaign: String? = nil,
        term: String? = nil,
        content: String? = nil
    ) {
        SessionManager.shared.setUTMParameters(
            source: source,
            medium: medium,
            campaign: campaign,
            term: term,
            content: content
        )
    }

    /**
     * Clear all stored UTM attribution parameters
     */
    public static func clearUTMParameters() {
        SessionManager.shared.clearUTMParameters()
    }

    /**
     * Start a new session (resets all session counters)
     */
    public static func startNewSession() {
        SessionManager.shared.startNewSession()
    }

    // MARK: - Device Information

    /**
     * Get device data
     *
     * - Returns: Device data with device information
     */
    public static func getDeviceData() -> DeviceData {
        let ctx = DeviceContext.shared
        let sm = SessionManager.shared
        return DeviceData(
            deviceId: sm.userId,
            deviceModel: ctx.deviceModel,
            osVersion: ctx.osVersion,
            appVersion: ctx.appVersion,
            screenWidth: ctx.screenWidth,
            screenHeight: ctx.screenHeight,
            language: ctx.languageCode,
            country: ctx.countryCode,
            isTablet: ctx.deviceType == "tablet",
            carrier: nil,
            networkType: nil
        )
    }

    // MARK: - Account Type

    /**
     * Set the account type for the current user
     *
     * Included in all analytics events. Defaults to "guest" if not set.
     * Valid values: "guest", "logged_in", "paid", "subscriber", "free"
     *
     * - Parameter accountType: The user's account type
     */
    public static func setAccountType(_ accountType: String) {
        guard _isInitialized else {
            BCLogger.warning("setAccountType called before initialization, ignoring")
            return
        }
        analyticsClient.setAccountType(accountType)
        BCLogger.debug("Account type set to: \(accountType)")
    }

    // MARK: - Privacy Compliance

    /**
     * Set GDPR consent string for privacy compliance
     *
     * - Parameter consent: GDPR consent string
     */
    public static func setGdprConsent(_ consent: String) {
        privacyStore.setGdprConsent(consent)
        BCLogger.debug("GDPR consent set")
    }

    /**
     * Set CCPA string for privacy compliance
     *
     * - Parameter ccpaString: CCPA consent string (e.g., "1YNN")
     */
    public static func setCcpaString(_ ccpaString: String) {
        privacyStore.setCcpaString(ccpaString)
        BCLogger.debug("CCPA string set: \(ccpaString)")
    }

    /**
     * Set COPPA compliance flag
     *
     * - Parameter isCompliant: true if the app should comply with COPPA
     */
    public static func setCoppaCompliant(_ isCompliant: Bool) {
        privacyStore.setCoppaApplies(isCompliant)

        // Also set in Google Mobile Ads
        GoogleMobileAds.MobileAds.shared.requestConfiguration.tagForChildDirectedTreatment = isCompliant ? true : false
        BCLogger.debug("COPPA compliance set to: \(isCompliant)")
    }

    // MARK: - Debug and Testing

    /**
     * Enable or disable debug mode
     *
     * - Parameter enabled: true to enable debug logging
     */
    public static func setDebugMode(_ enabled: Bool) {
        debugMode = enabled
        BCLogger.isEnabled = enabled
        BCLogger.debug("Debug mode set to: \(enabled)")
    }

    /**
     * Add a test device ID for Google Ads testing
     *
     * - Parameter deviceId: Test device ID from Google Ads logs
     */
    public static func addTestDevice(_ deviceId: String) {
        if !testDeviceIds.contains(deviceId) {
            testDeviceIds.append(deviceId)
        }
        GoogleMobileAds.MobileAds.shared.requestConfiguration.testDeviceIdentifiers = testDeviceIds
        BCLogger.debug("Added test device: \(deviceId)")
    }

    /**
     * Remove a test device ID
     *
     * - Parameter deviceId: Test device ID to remove
     */
    public static func removeTestDevice(_ deviceId: String) {
        testDeviceIds.removeAll { $0 == deviceId }
        GoogleMobileAds.MobileAds.shared.requestConfiguration.testDeviceIdentifiers = testDeviceIds
        BCLogger.debug("Removed test device: \(deviceId)")
    }

    /**
     * Get list of test device IDs
     *
     * - Returns: List of currently configured test device IDs
     */
    public static func getTestDevices() -> [String] {
        return testDeviceIds
    }

    // MARK: - State Queries

    /**
     * Check if the SDK has been initialized
     *
     * - Returns: true if initialized, false otherwise
     */
    public static func isSDKInitialized() -> Bool {
        return _isInitialized
    }

    /**
     * Check if configuration has been loaded
     *
     * - Returns: true if configuration has been successfully loaded
     */
    public static func isConfigReady() -> Bool {
        guard _isInitialized else { return false }
        return configManager?.getCachedConfig() != nil
    }

    /**
     * Track screen view for analytics (alias for Android API compatibility)
     *
     * - Parameters:
     *   - screenName: Name of the screen being viewed
     *   - options: Optional overrides for page URL, content metadata, and custom dimensions
     */
    public static func trackScreenView(_ screenName: String, options: ScreenViewOptions? = nil) {
        trackScreen(screenName, options: options)
    }

    // MARK: - Internal

    internal static func requireInitialized() {
        precondition(_isInitialized, "BigCrunchAds SDK not initialized. Call initialize() first.")
    }

    internal static func getConfigManager() -> ConfigManager {
        requireInitialized()
        return configManager
    }

    internal static func getAnalyticsClient() -> AnalyticsClient {
        requireInitialized()
        return analyticsClient
    }

    internal static func getBidRequestClient() -> BidRequestClient? {
        return bidRequestClient
    }

    // MARK: - Testing Support

    /// Reset the SDK state for testing purposes only
    /// - Warning: This is for unit tests only. Do not call in production code.
    internal static func resetForTesting() {
        _isInitialized = false
        configManager = nil
        analyticsClient = nil
        propertyId = ""
        environment = .prod
        BCLogger.isEnabled = false
    }
}
