/**
 * Content Security Service
 * Provides unified methods for text and image content security checking
 * via WeChat's msgSecCheck and imgSecCheck APIs.
 *
 * Features:
 * - checkTextContent(openid, content, scene): Text content audit
 * - checkImageContent(imageBuffer): Image content audit
 * - 3-second timeout for each API call
 * - Rate limiting queue (5000 requests/minute for msgSecCheck)
 */

const https = require('https');
const AuditLogger = require('./auditLogger');

// ============================================================
// Configuration
// ============================================================
const MSG_SEC_CHECK_URL = 'https://api.weixin.qq.com/wxa/msg_sec_check';
const IMG_SEC_CHECK_URL = 'https://api.weixin.qq.com/wxa/img_sec_check';
const API_TIMEOUT_MS = 3000; // 3 seconds timeout
const RATE_LIMIT_PER_MINUTE = 5000;
const RATE_LIMIT_WINDOW_MS = 60000; // 1 minute

// Scene mapping for msgSecCheck
const SCENE_MAP = {
  NICKNAME: 1,
  CHAT: 2,
  SIGNATURE: 3,
  DESCRIPTION: 4,
};

class ContentSecurityService {
  /**
   * @param {object} options
   * @param {import('./wechatTokenManager')} options.tokenManager - Token manager instance
   * @param {object} [options.logger] - Optional custom logger
   */
  constructor(options = {}) {
    this.tokenManager = options.tokenManager;
    this.logger = options.logger || new AuditLogger();

    // Rate limiting
    this._requestTimestamps = [];
    this._requestQueue = [];
    this._isProcessingQueue = false;
  }

  /**
   * Check text content for security violations using msgSecCheck.
   * @param {string} openid - User's openid
   * @param {string} content - Text content to check
   * @param {number} scene - Scene value (1=nickname, 2=chat, 3=signature, 4=description)
   * @returns {Promise<{pass: boolean, errcode: number, errmsg: string, suggest: string, label: number}>}
   */
  async checkTextContent(openid, content, scene) {
    const startTime = Date.now();

    // Validate scene value
    if (!Object.values(SCENE_MAP).includes(scene)) {
      const result = {
        pass: false,
        errcode: -1,
        errmsg: 'Invalid scene value',
        suggest: 'risky',
        label: 100,
      };
      this.logger.logAudit({
        userId: openid,
        contentType: 'text',
        contentSummary: this._sanitizeContent(content),
        scene,
        result: 'rejected',
        reason: 'invalid_scene',
        duration: Date.now() - startTime,
      });
      return result;
    }

    // Check if token manager is available
    if (!this.tokenManager || !this.tokenManager.isAvailable()) {
      const result = {
        pass: false,
        errcode: -1,
        errmsg: '审核服务暂时不可用，请稍后再试',
        suggest: 'risky',
        label: 100,
      };
      this.logger.logAudit({
        userId: openid,
        contentType: 'text',
        contentSummary: this._sanitizeContent(content),
        scene,
        result: 'rejected',
        reason: 'service_unavailable',
        duration: Date.now() - startTime,
      });
      return result;
    }

    // Enforce rate limiting
    await this._enforceRateLimit();

    try {
      const token = await this.tokenManager.getAccessToken();
      if (!token) {
        const result = {
          pass: false,
          errcode: -1,
          errmsg: '审核服务暂时不可用，请稍后再试',
          suggest: 'risky',
          label: 100,
        };
        this.logger.logAudit({
          userId: openid,
          contentType: 'text',
          contentSummary: this._sanitizeContent(content),
          scene,
          result: 'rejected',
          reason: 'token_unavailable',
          duration: Date.now() - startTime,
        });
        return result;
      }

      const url = `${MSG_SEC_CHECK_URL}?access_token=${token}`;
      const postData = JSON.stringify({
        openid,
        scene,
        version: 2,
        content,
      });

      const apiResult = await this._callWechatAPI(url, postData, 'msgSecCheck');
      const duration = Date.now() - startTime;

      const pass = apiResult.errcode === 0 && apiResult.result && apiResult.result.suggest === 'pass';

      const result = {
        pass,
        errcode: apiResult.errcode || 0,
        errmsg: apiResult.errmsg || '',
        suggest: apiResult.result ? apiResult.result.suggest : 'risky',
        label: apiResult.result ? apiResult.result.label : 100,
      };

      this.logger.logAudit({
        userId: openid,
        contentType: 'text',
        contentSummary: this._sanitizeContent(content),
        scene,
        result: pass ? 'pass' : 'reject',
        reason: pass ? 'content_safe' : `label_${result.label}`,
        duration,
      });

      return result;
    } catch (err) {
      const duration = Date.now() - startTime;
      console.error('[ContentSecurity] msgSecCheck error:', err.message);

      this.logger.logAudit({
        userId: openid,
        contentType: 'text',
        contentSummary: this._sanitizeContent(content),
        scene,
        result: 'error',
        reason: err.message,
        duration,
      });

      // On error, reject the content (fail-closed: safety over availability)
      return {
        pass: false,
        errcode: -1,
        errmsg: '审核服务暂时不可用，请稍后再试',
        suggest: 'risky',
        label: 100,
      };
    }
  }

  /**
   * Check image content for security violations using imgSecCheck.
   * @param {Buffer} imageBuffer - Image data buffer
   * @returns {Promise<{pass: boolean, errcode: number, errmsg: string}>}
   */
  async checkImageContent(imageBuffer) {
    const startTime = Date.now();

    // Check image size (max 1MB)
    if (imageBuffer.length > 1024 * 1024) {
      const result = {
        pass: false,
        errcode: -1,
        errmsg: '图片大小不能超过1MB',
      };
      this.logger.logAudit({
        userId: 'system',
        contentType: 'image',
        contentSummary: `image_${imageBuffer.length}bytes`,
        scene: 0,
        result: 'rejected',
        reason: 'image_too_large',
        duration: Date.now() - startTime,
      });
      return result;
    }

    // Check if token manager is available
    if (!this.tokenManager || !this.tokenManager.isAvailable()) {
      const result = {
        pass: false,
        errcode: -1,
        errmsg: '审核服务暂时不可用，请稍后再试',
      };
      this.logger.logAudit({
        userId: 'system',
        contentType: 'image',
        contentSummary: `image_${imageBuffer.length}bytes`,
        scene: 0,
        result: 'rejected',
        reason: 'service_unavailable',
        duration: Date.now() - startTime,
      });
      return result;
    }

    // Enforce rate limiting
    await this._enforceRateLimit();

    try {
      const token = await this.tokenManager.getAccessToken();
      if (!token) {
        const result = {
          pass: false,
          errcode: -1,
          errmsg: '审核服务暂时不可用，请稍后再试',
        };
        this.logger.logAudit({
          userId: 'system',
          contentType: 'image',
          contentSummary: `image_${imageBuffer.length}bytes`,
          scene: 0,
          result: 'rejected',
          reason: 'token_unavailable',
          duration: Date.now() - startTime,
        });
        return result;
      }

      const url = `${IMG_SEC_CHECK_URL}?access_token=${token}`;

      // Build multipart form data for imgSecCheck
      const boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW';
      const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="media"; filename="image.png"\r\nContent-Type: image/png\r\n\r\n`;
      const suffix = `\r\n--${boundary}--\r\n`;
      const buffer = Buffer.concat([
        Buffer.from(prefix),
        imageBuffer,
        Buffer.from(suffix),
      ]);

      const apiResult = await this._callWechatAPIWithBuffer(url, buffer, boundary, 'imgSecCheck');
      const duration = Date.now() - startTime;

      const pass = apiResult.errcode === 0;

      this.logger.logAudit({
        userId: 'system',
        contentType: 'image',
        contentSummary: `image_${imageBuffer.length}bytes`,
        scene: 0,
        result: pass ? 'pass' : 'reject',
        reason: pass ? 'image_safe' : `errcode_${apiResult.errcode}`,
        duration,
      });

      return {
        pass,
        errcode: apiResult.errcode || 0,
        errmsg: pass ? 'ok' : '图片内容违规，请更换',
      };
    } catch (err) {
      const duration = Date.now() - startTime;
      console.error('[ContentSecurity] imgSecCheck error:', err.message);

      this.logger.logAudit({
        userId: 'system',
        contentType: 'image',
        contentSummary: `image_${imageBuffer.length}bytes`,
        scene: 0,
        result: 'error',
        reason: err.message,
        duration,
      });

      // Fail-closed
      return {
        pass: false,
        errcode: -1,
        errmsg: '审核服务暂时不可用，请稍后再试',
      };
    }
  }

  /**
   * Call WeChat API with JSON POST data.
   * @param {string} url
   * @param {string} postData
   * @param {string} apiName - For logging
   * @returns {Promise<object>}
   */
  _callWechatAPI(url, postData, apiName) {
    return new Promise((resolve, reject) => {
      const parsedUrl = new URL(url);
      const options = {
        hostname: parsedUrl.hostname,
        path: parsedUrl.pathname + parsedUrl.search,
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Content-Length': Buffer.byteLength(postData),
        },
        timeout: API_TIMEOUT_MS,
      };

      const req = https.request(options, (res) => {
        let data = '';
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          try {
            const result = JSON.parse(data);
            resolve(result);
          } catch (e) {
            reject(new Error(`Invalid JSON from ${apiName}: ${data.substring(0, 100)}`));
          }
        });
      });

      req.on('timeout', () => {
        req.destroy();
        reject(new Error(`${apiName} request timeout (${API_TIMEOUT_MS}ms)`));
      });

      req.on('error', (err) => {
        reject(err);
      });

      req.write(postData);
      req.end();
    });
  }

  /**
   * Call WeChat API with multipart form data (for image upload).
   * @param {string} url
   * @param {Buffer} buffer
   * @param {string} boundary
   * @param {string} apiName - For logging
   * @returns {Promise<object>}
   */
  _callWechatAPIWithBuffer(url, buffer, boundary, apiName) {
    return new Promise((resolve, reject) => {
      const parsedUrl = new URL(url);
      const options = {
        hostname: parsedUrl.hostname,
        path: parsedUrl.pathname + parsedUrl.search,
        method: 'POST',
        headers: {
          'Content-Type': `multipart/form-data; boundary=${boundary}`,
          'Content-Length': buffer.length,
        },
        timeout: API_TIMEOUT_MS,
      };

      const req = https.request(options, (res) => {
        let data = '';
        res.on('data', (chunk) => { data += chunk; });
        res.on('end', () => {
          try {
            const result = JSON.parse(data);
            resolve(result);
          } catch (e) {
            reject(new Error(`Invalid JSON from ${apiName}: ${data.substring(0, 100)}`));
          }
        });
      });

      req.on('timeout', () => {
        req.destroy();
        reject(new Error(`${apiName} request timeout (${API_TIMEOUT_MS}ms)`));
      });

      req.on('error', (err) => {
        reject(err);
      });

      req.write(buffer);
      req.end();
    });
  }

  /**
   * Enforce rate limiting: queue requests if exceeding 5000/min.
   * @returns {Promise<void>}
   */
  async _enforceRateLimit() {
    const now = Date.now();

    // Clean old timestamps outside the current window
    this._requestTimestamps = this._requestTimestamps.filter(
      (ts) => now - ts < RATE_LIMIT_WINDOW_MS
    );

    if (this._requestTimestamps.length < RATE_LIMIT_PER_MINUTE) {
      this._requestTimestamps.push(now);
      return;
    }

    // Rate limited - wait until a slot opens
    return new Promise((resolve) => {
      this._requestQueue.push(resolve);
      this._processQueue();
    });
  }

  /**
   * Process queued requests as rate limit slots become available.
   */
  _processQueue() {
    if (this._isProcessingQueue || this._requestQueue.length === 0) return;
    this._isProcessingQueue = true;

    const processNext = () => {
      const now = Date.now();
      this._requestTimestamps = this._requestTimestamps.filter(
        (ts) => now - ts < RATE_LIMIT_WINDOW_MS
      );

      if (this._requestTimestamps.length < RATE_LIMIT_PER_MINUTE && this._requestQueue.length > 0) {
        this._requestTimestamps.push(now);
        const nextResolve = this._requestQueue.shift();
        nextResolve();
        processNext();
      } else if (this._requestQueue.length > 0) {
        // Wait for the oldest timestamp to expire
        const oldestTs = this._requestTimestamps[0];
        const waitTime = RATE_LIMIT_WINDOW_MS - (now - oldestTs) + 100;
        setTimeout(processNext, waitTime);
      } else {
        this._isProcessingQueue = false;
      }
    };

    processNext();
  }

  /**
   * Sanitize content for logging: keep only first 3 and last 3 chars,
   * replace middle with ***.
   * @param {string} content
   * @returns {string}
   */
  _sanitizeContent(content) {
    if (!content || typeof content !== 'string') return '';
    if (content.length <= 6) return content;
    return content.substring(0, 3) + '***' + content.substring(content.length - 3);
  }
}

// Export scene mapping for external use
ContentSecurityService.SCENE_MAP = SCENE_MAP;

module.exports = ContentSecurityService;