import UIKit
import GoogleMobileAds

/**
 * BigCrunch Banner View - UI component for displaying banner ads
 *
 * Usage:
 * ```swift
 * let bannerView = BigCrunchBannerView()
 * bannerView.configure(placementId: "home_banner")
 * bannerView.delegate = self
 * bannerView.loadAd(rootViewController: self)
 *
 * // Add to view hierarchy
 * view.addSubview(bannerView)
 *
 * // When done:
 * bannerView.destroy()
 * ```
 *
 * Implement BigCrunchBannerViewDelegate for ad events:
 * ```swift
 * extension ViewController: BigCrunchBannerViewDelegate {
 *     func bannerViewDidLoadAd(_ bannerView: BigCrunchBannerView) { }
 *     func bannerView(_ bannerView: BigCrunchBannerView, didFailToLoadWithError error: String) { }
 *     func bannerViewDidRecordClick(_ bannerView: BigCrunchBannerView) { }
 *     func bannerViewDidRecordImpression(_ bannerView: BigCrunchBannerView) { }
 * }
 * ```
 */
public class BigCrunchBannerView: UIView {

    // MARK: - Public Properties

    /**
     * Delegate for receiving banner ad events
     */
    public weak var delegate: BigCrunchBannerViewDelegate?

    /**
     * The configured placement ID
     */
    public private(set) var placementId: String?

    /**
     * Override ad size (optional)
     *
     * If set, this size will be used instead of the size from backend config.
     * Set this before calling loadAd().
     * Use `AdSize.adaptive()` for adaptive banners that fill the screen width.
     */
    public var adSizeOverride: AdSize?

    // MARK: - Private Properties

    private static let TAG = "BigCrunchBannerView"
    private static let MIN_REFRESH_INTERVAL_MS = 10000 // 10 seconds minimum
    private var isConfigured = false
    private var isDestroyed = false
    private var isPaused = false
    private var adOrchestrator: AdOrchestrator?
    private var internalBannerView: GoogleMobileAds.BannerView?

    // Refresh properties
    private var refreshTimer: Timer?
    private var refreshCount: Int = 0
    private var effectiveRefreshConfig: RefreshConfig?
    private weak var rootViewControllerForRefresh: UIViewController?

    // MARK: - Initialization

    public override init(frame: CGRect) {
        super.init(frame: frame)
        BCLogger.verbose("\(BigCrunchBannerView.TAG): BigCrunchBannerView created")
    }

    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        BCLogger.verbose("\(BigCrunchBannerView.TAG): BigCrunchBannerView created from coder")
    }

    deinit {
        if !isDestroyed {
            BCLogger.verbose("\(BigCrunchBannerView.TAG): Auto-destroying banner in deinit")
            destroy()
        }
    }

    // MARK: - Public Methods

    /**
     * Configure the banner view with a placement ID
     *
     * - Parameter placementId: The placement ID from the BigCrunch dashboard
     */
    public func configure(placementId: String) {
        if isDestroyed {
            BCLogger.warning("\(BigCrunchBannerView.TAG): Cannot configure destroyed banner view")
            return
        }

        guard !placementId.isEmpty else {
            BCLogger.error("\(BigCrunchBannerView.TAG): Invalid placementId: cannot be empty")
            return
        }

        self.placementId = placementId
        self.isConfigured = true
        BCLogger.debug("\(BigCrunchBannerView.TAG): Banner configured with placement: \(placementId)")
    }

    /**
     * Load a banner ad
     *
     * Must call `configure(placementId:)` before calling this method.
     * The delegate will be notified of load success or failure.
     *
     * - Parameter rootViewController: The view controller to use for presenting ad click actions
     */
    public func loadAd(rootViewController: UIViewController) {
        if isDestroyed {
            BCLogger.warning("\(BigCrunchBannerView.TAG): Cannot load ad on destroyed banner view")
            delegate?.bannerView(self, didFailToLoadWithError: "Banner view has been destroyed")
            return
        }

        guard isConfigured, let pid = placementId else {
            BCLogger.error("\(BigCrunchBannerView.TAG): Banner not configured. Call configure() first.")
            delegate?.bannerView(self, didFailToLoadWithError: "Banner not configured. Call configure() first.")
            return
        }

        BCLogger.debug("\(BigCrunchBannerView.TAG): Loading banner ad: \(pid) (refreshCount: \(refreshCount))")

        guard BigCrunchAds.isInitialized() else {
            BCLogger.error("\(BigCrunchBannerView.TAG): SDK not initialized")
            delegate?.bannerView(self, didFailToLoadWithError: "SDK not initialized. Call BigCrunchAds.initialize() first.")
            return
        }

        // Store root view controller for refresh
        rootViewControllerForRefresh = rootViewController

        // Resolve effective refresh config on first load
        if effectiveRefreshConfig == nil {
            let configManager = BigCrunchAds.getConfigManager()
            effectiveRefreshConfig = configManager.getEffectiveRefreshConfig(placementId: pid)
            if let config = effectiveRefreshConfig {
                BCLogger.debug("\(BigCrunchBannerView.TAG): Refresh config resolved - enabled: \(config.enabled), interval: \(config.intervalMs)ms, max: \(config.maxRefreshes)")
            }
        }

        // Create AdOrchestrator lazily
        if adOrchestrator == nil {
            adOrchestrator = createAdOrchestrator()
        }

        // Create callback wrapper
        let callback = BannerCallbackImpl(bannerView: self)

        // Load the ad through the orchestrator
        _ = adOrchestrator?.loadBannerAd(
            placementId: pid,
            rootViewController: rootViewController,
            callback: callback,
            refreshCount: refreshCount,
            adSizeOverride: adSizeOverride,
            bannerViewSetter: { [weak self] bannerView in
                self?.setInternalBannerView(bannerView) ?? false
            }
        )
    }

    /**
     * Load and display the banner ad (legacy method without root view controller)
     *
     * Note: This method attempts to find the root view controller automatically.
     * For better control, use loadAd(rootViewController:) instead.
     */
    public func loadAd() {
        guard BigCrunchAds.isInitialized() else {
            BCLogger.error("\(BigCrunchBannerView.TAG): SDK not initialized")
            delegate?.bannerView(self, didFailToLoadWithError: "SDK not initialized. Call BigCrunchAds.initialize() first.")
            return
        }

        // Try to find the root view controller
        guard let rootVC = findRootViewController() else {
            BCLogger.error("\(BigCrunchBannerView.TAG): Could not find root view controller")
            delegate?.bannerView(self, didFailToLoadWithError: "Could not find root view controller. Use loadAd(rootViewController:) instead.")
            return
        }

        loadAd(rootViewController: rootVC)
    }

    /**
     * Pause ad refresh
     *
     * Stops the refresh timer. Call `resume()` to restart.
     */
    public func pause() {
        isPaused = true
        cancelRefreshTimer()
        BCLogger.debug("\(BigCrunchBannerView.TAG): Banner ad refresh paused")
    }

    /**
     * Resume ad refresh
     *
     * Restarts the refresh timer if refresh is enabled and max refreshes not reached.
     */
    public func resume() {
        isPaused = false
        BCLogger.debug("\(BigCrunchBannerView.TAG): Banner ad refresh resumed")
        scheduleRefresh()
    }

    /**
     * Destroy the banner view and release resources
     *
     * Call this when the banner is no longer needed.
     */
    public func destroy() {
        if isDestroyed {
            BCLogger.verbose("\(BigCrunchBannerView.TAG): Banner already destroyed")
            return
        }

        BCLogger.debug("\(BigCrunchBannerView.TAG): Destroying banner: \(placementId ?? "nil")")

        // Mark destroyed FIRST so an in-flight load (suspended at the S2S fetch)
        // or a racing refresh timer sees the flag and bails out
        isDestroyed = true

        // Stop refresh timer
        cancelRefreshTimer()

        if let pid = placementId {
            adOrchestrator?.destroyBannerAd(placementId: pid)
        }

        // Remove internal banner view
        internalBannerView?.removeFromSuperview()
        internalBannerView = nil

        // Clear references
        delegate = nil
        adOrchestrator = nil
        rootViewControllerForRefresh = nil
        effectiveRefreshConfig = nil
        refreshCount = 0
    }

    /**
     * Check if the banner view has been destroyed
     */
    public func isDestroyedState() -> Bool {
        return isDestroyed
    }

    /**
     * Get the current refresh count
     */
    public func getRefreshCount() -> Int {
        return refreshCount
    }

    // MARK: - Internal Methods

    /// Attach a freshly loaded GMA banner view. Returns false (refusing the view)
    /// if this container was destroyed while the load was in flight.
    internal func setInternalBannerView(_ bannerView: GoogleMobileAds.BannerView) -> Bool {
        guard !isDestroyed else {
            BCLogger.debug("\(BigCrunchBannerView.TAG): Rejecting banner view delivered after destroy")
            return false
        }

        internalBannerView?.removeFromSuperview()
        internalBannerView = bannerView

        // Add to view hierarchy
        bannerView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(bannerView)

        NSLayoutConstraint.activate([
            bannerView.topAnchor.constraint(equalTo: topAnchor),
            bannerView.bottomAnchor.constraint(equalTo: bottomAnchor),
            bannerView.leadingAnchor.constraint(equalTo: leadingAnchor),
            bannerView.trailingAnchor.constraint(equalTo: trailingAnchor)
        ])

        return true
    }

    /// Called by the callback when an ad loads successfully. Schedules the next refresh.
    internal func onAdLoadedForRefresh() {
        scheduleRefresh()
    }

    // MARK: - Refresh Logic

    private func scheduleRefresh() {
        // Don't schedule if destroyed or paused
        guard !isDestroyed, !isPaused else { return }

        guard let config = effectiveRefreshConfig, config.enabled else { return }

        // Check max refreshes limit
        guard refreshCount < config.maxRefreshes else {
            BCLogger.debug("\(BigCrunchBannerView.TAG): Max refreshes reached (\(config.maxRefreshes)) for \(placementId ?? "")")
            return
        }

        // Enforce minimum interval
        let intervalMs = max(config.intervalMs, BigCrunchBannerView.MIN_REFRESH_INTERVAL_MS)
        let intervalSeconds = Double(intervalMs) / 1000.0

        // Cancel any existing timer
        cancelRefreshTimer()

        BCLogger.debug("\(BigCrunchBannerView.TAG): Scheduling refresh in \(intervalSeconds)s (count: \(refreshCount)/\(config.maxRefreshes)) for \(placementId ?? "")")

        refreshTimer = Timer.scheduledTimer(withTimeInterval: intervalSeconds, repeats: false) { [weak self] _ in
            self?.performRefresh()
        }
    }

    private func performRefresh() {
        guard !isDestroyed, !isPaused else { return }

        guard let rootVC = rootViewControllerForRefresh else {
            BCLogger.warning("\(BigCrunchBannerView.TAG): Cannot refresh - root view controller is nil")
            return
        }

        refreshCount += 1
        BCLogger.debug("\(BigCrunchBannerView.TAG): Refreshing banner ad (refresh #\(refreshCount)) for \(placementId ?? "")")

        loadAd(rootViewController: rootVC)
    }

    private func cancelRefreshTimer() {
        refreshTimer?.invalidate()
        refreshTimer = nil
    }

    // MARK: - Private Methods

    private func createAdOrchestrator() -> AdOrchestrator {
        let configManager = BigCrunchAds.getConfigManager()
        let analyticsClient = BigCrunchAds.getAnalyticsClient()
        let googleAdsAdapter = GoogleAdsAdapter(analyticsClient: analyticsClient)

        // BidRequestClient is shared across all views for batching
        // If S2S is disabled, create a no-op client
        let bidRequestClient = BigCrunchAds.getBidRequestClient() ?? BidRequestClient(
            httpClient: HTTPClient(),
            configManager: configManager,
            privacyStore: BigCrunchAds.privacyStore,
            s2sConfig: S2SConfig(enabled: false, serverUrl: "", timeoutMs: 0)
        )

        return AdOrchestrator(
            configManager: configManager,
            analyticsClient: analyticsClient,
            bidRequestClient: bidRequestClient,
            googleAdsAdapter: googleAdsAdapter
        )
    }

    private func findRootViewController() -> UIViewController? {
        // Try to find key window and its root view controller
        if #available(iOS 15.0, *) {
            return UIApplication.shared.connectedScenes
                .compactMap { $0 as? UIWindowScene }
                .flatMap { $0.windows }
                .first { $0.isKeyWindow }?
                .rootViewController
        } else {
            return UIApplication.shared.windows
                .first { $0.isKeyWindow }?
                .rootViewController
        }
    }
}

// MARK: - Delegate Protocol

/**
 * Delegate protocol for BigCrunchBannerView events
 */
public protocol BigCrunchBannerViewDelegate: AnyObject {
    /**
     * Called when the ad has been successfully loaded
     */
    func bannerViewDidLoadAd(_ bannerView: BigCrunchBannerView)

    /**
     * Called when the ad fails to load
     */
    func bannerView(_ bannerView: BigCrunchBannerView, didFailToLoadWithError error: String)

    /**
     * Called when the user clicks on the ad
     */
    func bannerViewDidRecordClick(_ bannerView: BigCrunchBannerView)

    /**
     * Called when an impression is recorded for the ad
     */
    func bannerViewDidRecordImpression(_ bannerView: BigCrunchBannerView)
}

// MARK: - Default Implementations

public extension BigCrunchBannerViewDelegate {
    func bannerViewDidLoadAd(_ bannerView: BigCrunchBannerView) {}
    func bannerView(_ bannerView: BigCrunchBannerView, didFailToLoadWithError error: String) {}
    func bannerViewDidRecordClick(_ bannerView: BigCrunchBannerView) {}
    func bannerViewDidRecordImpression(_ bannerView: BigCrunchBannerView) {}
}

// MARK: - Internal Callback Implementation

private class BannerCallbackImpl: BannerCallback {
    private static let TAG = "BigCrunchBannerView"
    weak var bannerView: BigCrunchBannerView?

    init(bannerView: BigCrunchBannerView) {
        self.bannerView = bannerView
    }

    func onAdLoaded() {
        guard let bannerView = bannerView else { return }
        BCLogger.debug("\(BannerCallbackImpl.TAG): Banner ad loaded: \(bannerView.placementId ?? "")")
        bannerView.delegate?.bannerViewDidLoadAd(bannerView)
        // Schedule next refresh after successful load
        bannerView.onAdLoadedForRefresh()
    }

    func onAdFailedToLoad(error: String) {
        guard let bannerView = bannerView else { return }
        BCLogger.warning("\(BannerCallbackImpl.TAG): Banner ad failed to load: \(bannerView.placementId ?? "") - \(error)")
        bannerView.delegate?.bannerView(bannerView, didFailToLoadWithError: error)
        // Note: Do NOT schedule refresh on failure to avoid retry loops
    }

    func onAdClicked() {
        guard let bannerView = bannerView else { return }
        BCLogger.debug("\(BannerCallbackImpl.TAG): Banner ad clicked: \(bannerView.placementId ?? "")")
        bannerView.delegate?.bannerViewDidRecordClick(bannerView)
    }

    func onAdImpression() {
        guard let bannerView = bannerView else { return }
        BCLogger.debug("\(BannerCallbackImpl.TAG): Banner ad impression: \(bannerView.placementId ?? "")")
        bannerView.delegate?.bannerViewDidRecordImpression(bannerView)
    }
}
