// lib/services/ad_service.dart
import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

import 'ad_config.dart';
import 'ad_frequency_policy.dart';

/// Owns the interstitial/rewarded/banner lifecycle. Holds the
/// [AdFrequencyPolicy] and never shows an ad the policy or [adsRemoved]
/// forbids. All failures are swallowed — ads must never break gameplay.
///
/// [adsRemoved] is read through a getter callback so a mid-session purchase
/// takes effect without re-wiring the service.
class AdService {
  AdService({
    required bool Function() adsRemoved,
    AdFrequencyPolicy? policy,
    DateTime Function() now = DateTime.now,
  })  : _adsRemoved = adsRemoved,
        policy = policy ?? AdFrequencyPolicy(),
        _now = now;

  final bool Function() _adsRemoved;
  final AdFrequencyPolicy policy;
  final DateTime Function() _now;

  InterstitialAd? _interstitial;
  RewardedAd? _rewarded;
  bool _initialized = false;

  /// Flips true once the SDK is initialized. Banner slots that were built
  /// before consent finished listen to this and retry their load — otherwise
  /// the banner would stay empty until the screen is rebuilt.
  final ValueNotifier<bool> isReady = ValueNotifier<bool>(false);

  /// Called by ConsentService once the SDK is initialized. Preloads ads and
  /// notifies any waiting banner slots.
  void onSdkReady() {
    _initialized = true;
    _loadInterstitial();
    _loadRewarded();
    isReady.value = true;
  }

  // ---- lifecycle hooks the game calls ----
  void onRoundStart() => policy.onRoundStart();
  void onStageCompleted() => policy.onStageCompleted();

  void _loadInterstitial() {
    if (!_initialized || _adsRemoved()) return;
    InterstitialAd.load(
      adUnitId: AdConfig.interstitial,
      request: const AdRequest(),
      adLoadCallback: InterstitialAdLoadCallback(
        onAdLoaded: (ad) => _interstitial = ad,
        onAdFailedToLoad: (_) => _interstitial = null,
      ),
    );
  }

  void _loadRewarded() {
    if (!_initialized) return; // rewarded stays available even with adsRemoved
    RewardedAd.load(
      adUnitId: AdConfig.rewarded,
      request: const AdRequest(),
      rewardedAdLoadCallback: RewardedAdLoadCallback(
        onAdLoaded: (ad) => _rewarded = ad,
        onAdFailedToLoad: (_) => _rewarded = null,
      ),
    );
  }

  /// Shows a stage-end interstitial when the policy and adsRemoved allow it.
  /// No-op (and reloads) otherwise. Safe to call after every finished stage.
  void maybeShowInterstitial() {
    if (_adsRemoved()) return;
    final ad = _interstitial;
    if (ad == null || !policy.canShowInterstitial(_now())) {
      if (ad == null) _loadInterstitial();
      return;
    }
    _interstitial = null;
    ad.fullScreenContentCallback = FullScreenContentCallback(
      onAdDismissedFullScreenContent: (ad) {
        ad.dispose();
        _loadInterstitial();
      },
      onAdFailedToShowFullScreenContent: (ad, _) {
        ad.dispose();
        _loadInterstitial();
      },
    );
    policy.onInterstitialShown(_now());
    ad.show();
  }

  /// Shows a rewarded ad and resolves `true` when the reward is earned. If no
  /// ad is loaded, resolves `true` anyway — the rescue is a player benefit and
  /// must not be blocked by ad availability. A genuine dismissal-without-earn
  /// resolves `false`. Records the watch in the policy so it suppresses this
  /// round's interstitial.
  Future<bool> showRewarded() async {
    policy.onRewardedShown();
    final ad = _rewarded;
    if (ad == null) {
      _loadRewarded();
      return true; // no ad available -> grant the rescue
    }
    _rewarded = null;
    final completer = Completer<bool>();
    void finish(bool v) {
      if (!completer.isCompleted) completer.complete(v);
    }

    ad.fullScreenContentCallback = FullScreenContentCallback(
      onAdDismissedFullScreenContent: (ad) {
        ad.dispose();
        _loadRewarded();
        finish(false); // dismissed; earn already resolved true if it happened
      },
      onAdFailedToShowFullScreenContent: (ad, _) {
        ad.dispose();
        _loadRewarded();
        finish(true); // failed to present -> grant
      },
    );
    ad.show(onUserEarnedReward: (_, reward) => finish(true));
    return completer.future;
  }

  /// Creates a fresh banner for the home/map slot, or null when ads are
  /// removed. The caller passes a listener and owns disposal.
  BannerAd? createBanner({BannerAdListener? listener}) {
    if (_adsRemoved() || !_initialized) return null;
    return BannerAd(
      adUnitId: AdConfig.banner,
      size: AdSize.banner,
      request: const AdRequest(),
      listener: listener ?? const BannerAdListener(),
    )..load();
  }

  void dispose() {
    _interstitial?.dispose();
    _rewarded?.dispose();
    isReady.dispose();
  }
}
