import UIKit

/**
 * BigCrunch Interstitial Ads - Static API for full-screen interstitial ads
 *
 * Interstitial ads are full-screen ads that cover the interface of an app until
 * closed by the user. They're best used at natural transition points in the app.
 *
 * Usage:
 * ```swift
 * // 1. Preload the ad (do this early, e.g., when entering a screen)
 * BigCrunchInterstitial.preload(placementId: "article_interstitial") { result in
 *     switch result {
 *     case .success:
 *         print("Ad ready to show")
 *     case .failure(let error):
 *         print("Failed to load: \(error)")
 *     }
 * }
 *
 * // 2. Check if ready (optional)
 * if BigCrunchInterstitial.isReady(placementId: "article_interstitial") {
 *     // Ad is ready to show
 * }
 *
 * // 3. Show when appropriate (e.g., after completing an action)
 * BigCrunchInterstitial.show(
 *     from: self,
 *     placementId: "article_interstitial",
 *     delegate: self
 * )
 * ```
 *
 * Implement BigCrunchInterstitialDelegate for events:
 * ```swift
 * extension ViewController: BigCrunchInterstitialDelegate {
 *     func interstitialDidShow(placementId: String) { }
 *     func interstitialDidDismiss(placementId: String) { }
 *     func interstitialDidClick(placementId: String) { }
 *     func interstitialDidFail(placementId: String, error: String) { }
 * }
 * ```
 */
public final class BigCrunchInterstitial {

    private static let TAG = "BigCrunchInterstitial"
    private static var adOrchestrator: AdOrchestrator?
    private static let orchestratorLock = NSLock()

    // Store delegates to prevent deallocation during show
    private static var activeDelegates: [String: InterstitialDelegateWrapper] = [:]
    private static let delegateLock = NSLock()

    // Private initializer to prevent instantiation
    private init() {}

    // MARK: - Callback Protocol (Legacy)

    /**
     * Legacy callback protocol for interstitial ad events
     */
    public protocol Callback {
        func onAdShown()
        func onAdDismissed()
        func onAdFailed(error: String)
    }

    // MARK: - Preload Methods

    /**
     * Preload an interstitial ad for the given placement
     *
     * Call this early (e.g., when entering a screen) to give the ad time to load.
     *
     * - Parameters:
     *   - placementId: The placement ID from BigCrunch dashboard
     *   - completion: Optional completion handler called when preload finishes
     */
    public static func preload(
        placementId: String,
        completion: ((Result<Void, Error>) -> Void)? = nil
    ) {
        BCLogger.debug("\(TAG): Preloading interstitial: \(placementId)")

        guard BigCrunchAds.isInitialized() else {
            BCLogger.error("\(TAG): SDK not initialized")
            completion?(.failure(BigCrunchError.notInitialized))
            return
        }

        guard !placementId.isEmpty else {
            BCLogger.error("\(TAG): Invalid placementId: cannot be empty")
            completion?(.failure(BigCrunchError.invalidPlacement("placementId cannot be empty")))
            return
        }

        let orchestrator = getOrCreateOrchestrator()

        let callback = PreloadCallbackImpl(placementId: placementId, completion: completion)
        orchestrator.preloadInterstitialAd(placementId: placementId, callback: callback)
    }

    /**
     * Preload an interstitial ad (simplified version without completion)
     *
     * - Parameter placementId: The placement ID from BigCrunch dashboard
     */
    public static func preload(placementId: String) {
        preload(placementId: placementId, completion: nil)
    }

    // MARK: - Show Methods

    /**
     * Show a preloaded interstitial ad
     *
     * The ad must be preloaded first using `preload(placementId:)`. Check `isReady(placementId:)`
     * before calling this to ensure an ad is available.
     *
     * - Parameters:
     *   - viewController: The view controller to present the ad from
     *   - placementId: The placement ID
     *   - delegate: Optional delegate for ad events
     * - Returns: true if a preloaded ad was available and presentation was initiated;
     *   presentation failures are delivered asynchronously via the delegate
     */
    @discardableResult
    public static func show(
        from viewController: UIViewController,
        placementId: String,
        delegate: BigCrunchInterstitialDelegate? = nil
    ) -> Bool {
        BCLogger.debug("\(TAG): Showing interstitial: \(placementId)")

        guard BigCrunchAds.isInitialized() else {
            BCLogger.error("\(TAG): SDK not initialized")
            delegate?.interstitialDidFail(placementId: placementId, error: "SDK not initialized. Call BigCrunchAds.initialize() first.")
            return false
        }

        guard !placementId.isEmpty else {
            BCLogger.error("\(TAG): Invalid placementId: cannot be empty")
            delegate?.interstitialDidFail(placementId: placementId, error: "Invalid placementId")
            return false
        }

        let orchestrator = getOrCreateOrchestrator()

        // Create and store delegate wrapper
        let wrapper = InterstitialDelegateWrapper(placementId: placementId, delegate: delegate)

        delegateLock.lock()
        activeDelegates[placementId] = wrapper
        delegateLock.unlock()

        let callback = ShowCallbackImpl(placementId: placementId, delegate: delegate) { pid in
            // Cleanup delegate wrapper after ad is dismissed or fails
            delegateLock.lock()
            activeDelegates.removeValue(forKey: pid)
            delegateLock.unlock()
        }

        return orchestrator.showInterstitialAd(
            from: viewController,
            placementId: placementId,
            callback: callback
        )
    }

    /**
     * Show a preloaded interstitial ad (legacy API)
     *
     * - Parameters:
     *   - viewController: The view controller to present the ad from
     *   - placementId: The placement ID
     *   - callback: Legacy callback for ad events
     */
    public static func show(
        viewController: UIViewController,
        placementId: String,
        callback: Callback? = nil
    ) {
        let delegate: BigCrunchInterstitialDelegate? = callback.map { cb in
            LegacyCallbackAdapter(callback: cb)
        }
        show(from: viewController, placementId: placementId, delegate: delegate)
    }

    // MARK: - Query Methods

    /**
     * Check if an interstitial ad is ready to show
     *
     * - Parameter placementId: The placement ID
     * - Returns: true if an ad is preloaded and ready to show
     */
    public static func isReady(placementId: String) -> Bool {
        // Check if orchestrator exists (which means SDK was initialized and preload was called)
        guard let orchestrator = adOrchestrator else {
            return false
        }
        return orchestrator.isInterstitialReady(placementId: placementId)
    }

    /**
     * Clear all cached interstitial ads
     *
     * Call this to free up memory when ads are no longer needed.
     */
    public static func clearCache() {
        BCLogger.debug("\(TAG): Clearing interstitial cache")
        orchestratorLock.lock()
        adOrchestrator?.clearCache()
        orchestratorLock.unlock()
    }

    // MARK: - Private Methods

    private static func getOrCreateOrchestrator() -> AdOrchestrator {
        orchestratorLock.lock()
        defer { orchestratorLock.unlock() }

        if adOrchestrator == nil {
            let configManager = BigCrunchAds.getConfigManager()
            let analyticsClient = BigCrunchAds.getAnalyticsClient()
            let googleAdsAdapter = GoogleAdsAdapter(analyticsClient: analyticsClient)

            let bidRequestClient = BigCrunchAds.getBidRequestClient() ?? BidRequestClient(
                httpClient: HTTPClient(),
                configManager: configManager,
                privacyStore: BigCrunchAds.privacyStore,
                s2sConfig: S2SConfig(enabled: false, serverUrl: "", timeoutMs: 0)
            )

            adOrchestrator = AdOrchestrator(
                configManager: configManager,
                analyticsClient: analyticsClient,
                bidRequestClient: bidRequestClient,
                googleAdsAdapter: googleAdsAdapter
            )
        }
        return adOrchestrator!
    }

    // MARK: - Testing Support

    internal static func resetForTesting() {
        orchestratorLock.lock()
        adOrchestrator?.clearCache()
        adOrchestrator = nil
        orchestratorLock.unlock()

        delegateLock.lock()
        activeDelegates.removeAll()
        delegateLock.unlock()
    }
}

// MARK: - Delegate Protocol

/**
 * Delegate protocol for BigCrunchInterstitial events
 */
public protocol BigCrunchInterstitialDelegate: AnyObject {
    /**
     * Called when the interstitial ad is displayed on screen
     */
    func interstitialDidShow(placementId: String)

    /**
     * Called when the interstitial ad is dismissed by the user
     */
    func interstitialDidDismiss(placementId: String)

    /**
     * Called when the user clicks on the interstitial ad
     */
    func interstitialDidClick(placementId: String)

    /**
     * Called when the interstitial ad fails to show
     */
    func interstitialDidFail(placementId: String, error: String)
}

// MARK: - Default Implementations

public extension BigCrunchInterstitialDelegate {
    func interstitialDidShow(placementId: String) {}
    func interstitialDidDismiss(placementId: String) {}
    func interstitialDidClick(placementId: String) {}
    func interstitialDidFail(placementId: String, error: String) {}
}

// MARK: - Error Types

public enum BigCrunchError: Error, LocalizedError {
    case notInitialized
    case invalidPlacement(String)
    case noAdAvailable
    case loadFailed(String)

    public var errorDescription: String? {
        switch self {
        case .notInitialized:
            return "BigCrunchAds SDK not initialized. Call BigCrunchAds.initialize() first."
        case .invalidPlacement(let message):
            return "Invalid placement: \(message)"
        case .noAdAvailable:
            return "No ad available for this placement"
        case .loadFailed(let message):
            return "Failed to load ad: \(message)"
        }
    }
}

// MARK: - Private Helper Classes

private class InterstitialDelegateWrapper {
    let placementId: String
    weak var delegate: BigCrunchInterstitialDelegate?

    init(placementId: String, delegate: BigCrunchInterstitialDelegate?) {
        self.placementId = placementId
        self.delegate = delegate
    }
}

private class PreloadCallbackImpl: InterstitialCallback {
    let placementId: String
    let completion: ((Result<Void, Error>) -> Void)?

    init(placementId: String, completion: ((Result<Void, Error>) -> Void)?) {
        self.placementId = placementId
        self.completion = completion
    }

    func onAdLoaded() {
        BCLogger.debug("BigCrunchInterstitial: Interstitial preloaded: \(placementId)")
        completion?(.success(()))
    }

    func onAdFailedToLoad(error: String) {
        BCLogger.warning("BigCrunchInterstitial: Interstitial failed to preload: \(placementId) - \(error)")
        completion?(.failure(BigCrunchError.loadFailed(error)))
    }

    func onAdShowed() {}
    func onAdDismissed() {}
    func onAdClicked() {}
}

private class ShowCallbackImpl: InterstitialCallback {
    let placementId: String
    weak var delegate: BigCrunchInterstitialDelegate?
    let cleanup: (String) -> Void

    init(placementId: String, delegate: BigCrunchInterstitialDelegate?, cleanup: @escaping (String) -> Void) {
        self.placementId = placementId
        self.delegate = delegate
        self.cleanup = cleanup
    }

    func onAdLoaded() {
        // Not used during show
    }

    func onAdFailedToLoad(error: String) {
        BCLogger.warning("BigCrunchInterstitial: Interstitial failed to show: \(placementId) - \(error)")
        delegate?.interstitialDidFail(placementId: placementId, error: error)
        cleanup(placementId)
    }

    func onAdShowed() {
        BCLogger.debug("BigCrunchInterstitial: Interstitial showed: \(placementId)")
        delegate?.interstitialDidShow(placementId: placementId)
    }

    func onAdDismissed() {
        BCLogger.debug("BigCrunchInterstitial: Interstitial dismissed: \(placementId)")
        delegate?.interstitialDidDismiss(placementId: placementId)
        cleanup(placementId)
    }

    func onAdClicked() {
        BCLogger.debug("BigCrunchInterstitial: Interstitial clicked: \(placementId)")
        delegate?.interstitialDidClick(placementId: placementId)
    }
}

private class LegacyCallbackAdapter: BigCrunchInterstitialDelegate {
    let callback: BigCrunchInterstitial.Callback

    init(callback: BigCrunchInterstitial.Callback) {
        self.callback = callback
    }

    func interstitialDidShow(placementId: String) {
        callback.onAdShown()
    }

    func interstitialDidDismiss(placementId: String) {
        callback.onAdDismissed()
    }

    func interstitialDidClick(placementId: String) {
        // Not in legacy callback
    }

    func interstitialDidFail(placementId: String, error: String) {
        callback.onAdFailed(error: error)
    }
}
