/**
 * WeChat Access Token Manager
 * Manages the lifecycle of WeChat access tokens for content security APIs.
 * - Auto-refreshes tokens before expiry (2-hour validity, refresh 5 minutes early)
 * - Retry with exponential backoff on failure (2s, 4s, 8s)
 * - Marks service as unavailable after all retries exhausted
 */

const https = require('https');

// ============================================================
// Configuration
// ============================================================
const TOKEN_VALIDITY_MS = 2 * 60 * 60 * 1000; // 2 hours
const TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000; // Refresh 5 minutes before expiry
const RETRY_DELAYS = [2000, 4000, 8000]; // Exponential backoff delays in ms
const WECHAT_TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/token';

class WechatTokenManager {
  /**
   * @param {object} options
   * @param {string} options.appId - WeChat Mini Program App ID
   * @param {string} options.appSecret - WeChat Mini Program App Secret
   */
  constructor(options = {}) {
    this.appId = options.appId || process.env.WX_APPID || '';
    this.appSecret = options.appSecret || process.env.WX_APPSECRET || '';

    this._accessToken = null;
    this._expiresAt = 0;
    this._refreshTimer = null;
    this._isAvailable = false;
    this._isRefreshing = false;
    this._retryCount = 0;
  }

  /**
   * Initialize the token manager and fetch the first token.
   * @returns {Promise<boolean>} true if token obtained successfully
   */
  async init() {
    console.log('[TokenManager] Initializing...');
    const success = await this._refreshToken();
    if (success) {
      this._scheduleRefresh();
    }
    return success;
  }

  /**
   * Get the current valid access token.
   * If the token is expired or about to expire, refresh it first.
   * @returns {Promise<string|null>} The access token, or null if unavailable
   */
  async getAccessToken() {
    // If token is still valid (with margin), return it directly
    if (this._accessToken && Date.now() < this._expiresAt - TOKEN_REFRESH_MARGIN_MS) {
      return this._accessToken;
    }

    // Token expired or about to expire, try to refresh
    if (!this._isRefreshing) {
      const success = await this._refreshToken();
      if (success) {
        this._scheduleRefresh();
      }
    }

    return this._isAvailable ? this._accessToken : null;
  }

  /**
   * Check if the token manager is currently available.
   * @returns {boolean}
   */
  isAvailable() {
    return this._isAvailable;
  }

  /**
   * Refresh the access token by calling WeChat API.
   * Implements retry with exponential backoff.
   * @returns {Promise<boolean>}
   */
  async _refreshToken() {
    if (this._isRefreshing) {
      // Wait for the ongoing refresh to complete
      return new Promise((resolve) => {
        const checkInterval = setInterval(() => {
          if (!this._isRefreshing) {
            clearInterval(checkInterval);
            resolve(this._isAvailable);
          }
        }, 100);
      });
    }

    this._isRefreshing = true;

    for (let attempt = 0; attempt <= RETRY_DELAYS.length; attempt++) {
      try {
        const token = await this._fetchTokenFromWechat();
        if (token) {
          this._accessToken = token;
          this._expiresAt = Date.now() + TOKEN_VALIDITY_MS;
          this._isAvailable = true;
          this._retryCount = 0;
          this._isRefreshing = false;
          console.log('[TokenManager] Token refreshed successfully, expires at:',
            new Date(this._expiresAt).toISOString());
          return true;
        }
      } catch (err) {
        console.error(`[TokenManager] Fetch token failed (attempt ${attempt + 1}):`, err.message);
      }

      // Wait before retry (if there are more retries)
      if (attempt < RETRY_DELAYS.length) {
        const delay = RETRY_DELAYS[attempt];
        console.log(`[TokenManager] Retrying in ${delay}ms...`);
        await this._sleep(delay);
      }
    }

    // All retries exhausted
    this._isAvailable = false;
    this._isRefreshing = false;
    this._accessToken = null;
    console.error('[TokenManager] All retry attempts exhausted. Service marked as unavailable.');

    // Schedule a recovery attempt after 10 minutes
    setTimeout(() => {
      console.log('[TokenManager] Attempting recovery refresh...');
      this._refreshToken().then((success) => {
        if (success) {
          this._scheduleRefresh();
        }
      });
    }, 10 * 60 * 1000);

    return false;
  }

  /**
   * Fetch access token from WeChat API.
   * @returns {Promise<string|null>}
   */
  _fetchTokenFromWechat() {
    return new Promise((resolve, reject) => {
      if (!this.appId || !this.appSecret) {
        reject(new Error('AppId or AppSecret not configured'));
        return;
      }

      const url = `${WECHAT_TOKEN_URL}?grant_type=client_credential&appid=${this.appId}&secret=${this.appSecret}`;

      https.get(url, (res) => {
        let data = '';
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          try {
            const result = JSON.parse(data);
            if (result.access_token) {
              resolve(result.access_token);
            } else {
              reject(new Error(`WeChat API error: ${result.errcode} - ${result.errmsg}`));
            }
          } catch (e) {
            reject(new Error(`Invalid JSON response: ${data.substring(0, 100)}`));
          }
        });
      }).on('error', (err) => {
        reject(err);
      });
    });
  }

  /**
   * Schedule the next token refresh before expiry.
   */
  _scheduleRefresh() {
    if (this._refreshTimer) {
      clearTimeout(this._refreshTimer);
    }

    const refreshAt = this._expiresAt - TOKEN_REFRESH_MARGIN_MS;
    const delay = Math.max(refreshAt - Date.now(), 60000); // At least 1 minute

    console.log(`[TokenManager] Next refresh scheduled in ${Math.round(delay / 1000)}s`);

    this._refreshTimer = setTimeout(async () => {
      console.log('[TokenManager] Scheduled refresh triggered');
      const success = await this._refreshToken();
      if (success) {
        this._scheduleRefresh();
      }
    }, delay);
  }

  /**
   * Utility: sleep for a given number of milliseconds.
   * @param {number} ms
   * @returns {Promise<void>}
   */
  _sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  /**
   * Clean up resources.
   */
  destroy() {
    if (this._refreshTimer) {
      clearTimeout(this._refreshTimer);
      this._refreshTimer = null;
    }
    this._accessToken = null;
    this._isAvailable = false;
    console.log('[TokenManager] Destroyed');
  }
}

module.exports = WechatTokenManager;