/**
 * AdManager.js
 * Manages WeChat mini game ads: rewarded video and interstitial.
 * Supports scene-based ad triggering with per-scene cooldowns,
 * daily limits, preloading, and frequency control.
 */

/**
 * Ad scene types for rewarded video ads.
 * Each scene has independent cooldown and optional daily limits.
 */
const AD_SCENE = {
  REVIVE: 'REVIVE',               // Revive after death
  DOUBLE_REWARD: 'DOUBLE_REWARD', // Double settlement rewards
  DAILY_GOLD: 'DAILY_GOLD',       // Daily gold reward from main menu
};

/** Cooldown duration per scene in milliseconds (15 minutes). */
const SCENE_COOLDOWN_MS = 15 * 60 * 1000;

/** Daily limits for specific scenes. */
const SCENE_DAILY_LIMITS = {
  [AD_SCENE.DAILY_GOLD]: 3,
};

class AdManager {
  constructor() {
    /** @type {RewardedVideoAd|null} */
    this._rewardedVideo = null;
    /** @type {InterstitialAd|null} */
    this._interstitial = null;

    // Interstitial frequency control: show every N games since last show
    this._gamesSinceLastInterstitial = 0;
    this._interstitialFrequency = 3;

    // Ad unit IDs (replace with real IDs in production)
    this._rewardedVideoId = 'adunit-reward-placeholder';
    this._interstitialId = 'adunit-interstitial-placeholder';

    // State
    this._rewardedVideoReady = false;
    this._interstitialReady = false;
    this._adFreeEnabled = false; // purchased ad-free

    // Callback for rewarded video completion
    this._rewardCallback = null;

    // Scene cooldown tracking: Map<sceneType, lastShowTimestamp>
    this._sceneCooldowns = new Map();

    // Daily scene count tracking: Map<sceneType, { date: string, count: number }>
    this._sceneDailyCounts = new Map();

    // Skip ad initialization if using placeholder IDs (dev environment)
    this._isDevMode = this._rewardedVideoId.includes('placeholder') ||
                      this._interstitialId.includes('placeholder');

    if (!this._isDevMode) {
      this._init();
    } else {
      console.log('[AdManager] Dev mode: skipping ad initialization (placeholder IDs)');
    }

    // Restore daily counts from storage
    this._restoreDailyCounts();
  }

  /**
   * Initialize ad instances.
   * @private
   */
  _init() {
    // Check if ad-free was purchased
    try {
      if (GameGlobal.storageManager) {
        this._adFreeEnabled = GameGlobal.storageManager.hasPurchased('ad_free');
      }
    } catch (e) {}

    this._createRewardedVideo();
    this._createInterstitial();
  }

  /**
   * Create rewarded video ad instance.
   * @private
   */
  _createRewardedVideo() {
    try {
      if (typeof wx === 'undefined' || typeof wx.createRewardedVideoAd !== 'function') return;

      this._rewardedVideo = wx.createRewardedVideoAd({
        adUnitId: this._rewardedVideoId,
      });

      this._rewardedVideo.onLoad(() => {
        this._rewardedVideoReady = true;
        console.log('[AdManager] Rewarded video loaded');
      });

      this._rewardedVideo.onError((err) => {
        this._rewardedVideoReady = false;
        console.warn('[AdManager] Rewarded video error:', err);
      });

      this._rewardedVideo.onClose((res) => {
        // Dispatch reward instantly on ad close
        if (res && res.isEnded) {
          if (this._rewardCallback) {
            this._rewardCallback(true);
            this._rewardCallback = null;
          }
        } else {
          // User closed early
          if (this._rewardCallback) {
            this._rewardCallback(false);
            this._rewardCallback = null;
          }
        }
      });
    } catch (e) {
      console.warn('[AdManager] Failed to create rewarded video:', e);
    }
  }

  /**
   * Create interstitial ad instance.
   * @private
   */
  _createInterstitial() {
    if (this._adFreeEnabled) return;

    try {
      if (typeof wx === 'undefined' || typeof wx.createInterstitialAd !== 'function') return;

      this._interstitial = wx.createInterstitialAd({
        adUnitId: this._interstitialId,
      });

      this._interstitial.onLoad(() => {
        this._interstitialReady = true;
      });

      this._interstitial.onError((err) => {
        this._interstitialReady = false;
        console.warn('[AdManager] Interstitial error:', err);
      });

      this._interstitial.onClose(() => {
        this._interstitialReady = false;
      });
    } catch (e) {
      console.warn('[AdManager] Failed to create interstitial:', e);
    }
  }

  // ============================================================
  // Scene Cooldown & Daily Limit Helpers
  // ============================================================

  /**
   * Get today's date string (YYYY-MM-DD) for daily tracking.
   * @returns {string}
   * @private
   */
  _getTodayKey() {
    const d = new Date();
    return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
  }

  /**
   * Restore daily ad counts from StorageManager.
   * @private
   */
  _restoreDailyCounts() {
    try {
      if (GameGlobal && GameGlobal.storageManager) {
        const saved = GameGlobal.storageManager.get('ad_daily_counts', null);
        if (saved && typeof saved === 'object') {
          const today = this._getTodayKey();
          for (const [scene, data] of Object.entries(saved)) {
            if (data.date === today) {
              this._sceneDailyCounts.set(scene, { date: data.date, count: data.count });
            }
            // Stale dates are discarded
          }
        }
      }
    } catch (e) {}
  }

  /**
   * Persist daily ad counts to StorageManager.
   * @private
   */
  _saveDailyCounts() {
    try {
      if (GameGlobal && GameGlobal.storageManager) {
        const obj = {};
        for (const [scene, data] of this._sceneDailyCounts.entries()) {
          obj[scene] = data;
        }
        GameGlobal.storageManager.set('ad_daily_counts', obj);
      }
    } catch (e) {}
  }

  /**
   * Get the daily count for a scene.
   * @param {string} sceneType
   * @returns {number}
   * @private
   */
  _getDailyCount(sceneType) {
    const today = this._getTodayKey();
    const entry = this._sceneDailyCounts.get(sceneType);
    if (entry && entry.date === today) {
      return entry.count;
    }
    return 0;
  }

  /**
   * Increment the daily count for a scene.
   * @param {string} sceneType
   * @private
   */
  _incrementDailyCount(sceneType) {
    const today = this._getTodayKey();
    const entry = this._sceneDailyCounts.get(sceneType);
    if (entry && entry.date === today) {
      entry.count++;
    } else {
      this._sceneDailyCounts.set(sceneType, { date: today, count: 1 });
    }
    this._saveDailyCounts();
  }

  /**
   * Check if a scene ad can be shown (cooldown + daily limit).
   * @param {string} sceneType - One of AD_SCENE values.
   * @returns {boolean}
   */
  canShowScene(sceneType) {
    // Check cooldown
    const lastShow = this._sceneCooldowns.get(sceneType);
    if (lastShow && (Date.now() - lastShow < SCENE_COOLDOWN_MS)) {
      console.log(`[AdManager] Scene ${sceneType} is in cooldown`);
      return false;
    }

    // Check daily limit
    const limit = SCENE_DAILY_LIMITS[sceneType];
    if (limit !== undefined) {
      const count = this._getDailyCount(sceneType);
      if (count >= limit) {
        console.log(`[AdManager] Scene ${sceneType} daily limit reached (${count}/${limit})`);
        return false;
      }
    }

    return true;
  }

  /**
   * Get remaining daily count for a scene.
   * @param {string} sceneType
   * @returns {number} Remaining uses, or Infinity if no limit.
   */
  getRemainingDailyCount(sceneType) {
    const limit = SCENE_DAILY_LIMITS[sceneType];
    if (limit === undefined) return Infinity;
    return Math.max(0, limit - this._getDailyCount(sceneType));
  }

  // ============================================================
  // Public API
  // ============================================================

  /**
   * Preload the rewarded video ad (call during level loading).
   * Ensures the ad is ready when needed, reducing wait time.
   */
  preloadRewardedVideo() {
    if (!this._rewardedVideo) return;
    if (this._rewardedVideoReady) return; // Already loaded

    try {
      this._rewardedVideo.load().then(() => {
        console.log('[AdManager] Rewarded video preloaded');
      }).catch((err) => {
        console.warn('[AdManager] Rewarded video preload failed:', err);
      });
    } catch (e) {}
  }

  /**
   * Show a rewarded video ad for a specific scene.
   * Checks cooldown and daily limits before showing.
   * @param {string} sceneType - One of AD_SCENE values.
   * @param {Function} callback - Called with (completed: boolean) when ad closes.
   * @returns {boolean} Whether the ad was shown.
   */
  showRewardedVideoForScene(sceneType, callback) {
    if (!this.canShowScene(sceneType)) {
      if (callback) callback(false);
      return false;
    }

    const wrappedCallback = (completed) => {
      if (completed) {
        // Record cooldown timestamp
        this._sceneCooldowns.set(sceneType, Date.now());
        // Increment daily count
        this._incrementDailyCount(sceneType);
      }
      if (callback) callback(completed);
    };

    return this.showRewardedVideo(wrappedCallback);
  }

  /**
   * Show a rewarded video ad (low-level, no scene tracking).
   * @param {Function} callback - Called with (completed: boolean) when ad closes.
   * @returns {boolean} Whether the ad was shown (false if not ready).
   */
  showRewardedVideo(callback) {
    this._rewardCallback = callback;

    if (!this._rewardedVideo) {
      // Ad not available, give fallback
      console.warn('[AdManager] Rewarded video not available');
      if (callback) callback(false);
      return false;
    }

    this._rewardedVideo.show().catch(() => {
      // Try to reload and show again
      this._rewardedVideo.load().then(() => {
        this._rewardedVideo.show().catch(() => {
          console.warn('[AdManager] Failed to show rewarded video');
          if (callback) callback(false);
          this._rewardCallback = null;
        });
      }).catch(() => {
        if (callback) callback(false);
        this._rewardCallback = null;
      });
    });

    return true;
  }

  /**
   * Show an interstitial ad (respects frequency control and ad-free purchase).
   * Uses "games since last show" logic: shows after every N games.
   */
  showInterstitial() {
    if (this._adFreeEnabled) return;

    this._gamesSinceLastInterstitial++;

    if (this._gamesSinceLastInterstitial < this._interstitialFrequency) return;

    if (!this._interstitial || !this._interstitialReady) return;

    try {
      this._interstitial.show().then(() => {
        // Reset counter on successful show
        this._gamesSinceLastInterstitial = 0;
      }).catch(() => {
        // Silently skip on failure, don't block player flow
        console.warn('[AdManager] Failed to show interstitial, skipping');
      });
    } catch (e) {
      // Silently skip
    }
  }

  /**
   * Show a daily gold reward ad.
   * Convenience method for the DAILY_GOLD scene.
   * On completion, emits 'daily_gold_reward' event and adds 100 gold.
   * @param {Function} [callback] - Optional callback with (completed: boolean).
   * @returns {boolean} Whether the ad was shown.
   */
  showDailyGoldAd(callback) {
    return this.showRewardedVideoForScene(AD_SCENE.DAILY_GOLD, (completed) => {
      if (completed) {
        // Award 100 gold
        if (GameGlobal && GameGlobal.currencyManager) {
          GameGlobal.currencyManager.addGold(100);
        }
        // Emit event for UI update
        try {
          if (GameGlobal && GameGlobal.eventBus) {
            GameGlobal.eventBus.emit('daily_gold_reward', { amount: 100 });
          }
        } catch (e) {}
      }
      if (callback) callback(completed);
    });
  }

  /**
   * Get remaining daily gold ad claims for today.
   * @returns {number}
   */
  getDailyGoldRemaining() {
    return this.getRemainingDailyCount(AD_SCENE.DAILY_GOLD);
  }

  /**
   * Record that a game was played (for interstitial frequency).
   */
  recordGamePlayed() {
    // No-op: counting is now done inside showInterstitial()
    // Kept for backward compatibility
  }

  /**
   * Enable ad-free mode (after purchase).
   */
  enableAdFree() {
    this._adFreeEnabled = true;
    if (GameGlobal && GameGlobal.storageManager) {
      GameGlobal.storageManager.recordPurchase('ad_free');
    }
  }

  /** Whether rewarded video is ready. */
  get rewardedVideoReady() {
    return this._rewardedVideoReady;
  }

  /** Whether ad-free mode is enabled. */
  get adFreeEnabled() {
    return this._adFreeEnabled;
  }
}

// Export both the class and the scene enum
AdManager.AD_SCENE = AD_SCENE;

module.exports = AdManager;
