/**
 * contentSecurityManager.test.js
 * Unit tests for client-side ContentSecurityManager.
 *
 * Since ContentSecurityManager depends on wx APIs (wx.request, wx.getStorageSync, etc.),
 * we mock these APIs before requiring the module.
 */

const { describe, it, before, after, beforeEach } = require('node:test');
const assert = require('node:assert/strict');

// ============================================================
// Mock wx APIs
// ============================================================
const mockStorage = {};
const mockRequests = {};

global.wx = {
  getStorageSync: (key) => mockStorage[key] || '',
  setStorageSync: (key, value) => { mockStorage[key] = value; },
  request: (options) => {
    const key = `${options.method}:${options.url}`;
    if (mockRequests[key]) {
      const handler = mockRequests[key];
      setTimeout(() => {
        if (handler.success) {
          options.success(handler.success);
        } else if (handler.fail) {
          options.fail(handler.fail);
        }
      }, 0);
    } else {
      // Default: 404
      setTimeout(() => {
        options.fail({ errMsg: 'request:fail' });
      }, 0);
    }
  },
  uploadFile: (options) => {
    setTimeout(() => {
      options.fail({ errMsg: 'uploadFile:fail' });
    }, 0);
  },
  showToast: () => {},
  showKeyboard: () => {},
  hideKeyboard: () => {},
  onKeyboardInput: () => {},
  offKeyboardInput: () => {},
  onKeyboardConfirm: () => {},
  offKeyboardConfirm: () => {},
  chooseImage: () => {},
  getFileInfo: () => ({ size: 0 }),
};

// Now require the module after mocking wx
const ContentSecurityManager = require('../../js/managers/ContentSecurityManager');

// ============================================================
// Tests
// ============================================================

describe('ContentSecurityManager - Local Check', () => {
  let manager;

  before(async () => {
    manager = new ContentSecurityManager();
    await manager.init({ serverUrl: 'http://localhost:3000' });
  });

  it('should initialize with fallback word list', () => {
    assert.ok(manager.isInitialized());
    assert.ok(manager.getWordCount() > 0);
  });

  it('should detect sensitive words in content', () => {
    const result = manager.checkLocalText('这是一个赌博网站');
    assert.strictEqual(result.hasViolation, true);
    assert.ok(result.matchedWords.length > 0);
    assert.ok(result.matchedWords.includes('赌博'));
  });

  it('should detect political content', () => {
    const result = manager.checkLocalText('法轮功是邪教');
    assert.strictEqual(result.hasViolation, true);
    assert.ok(result.matchedWords.includes('法轮功'));
  });

  it('should detect pornographic content', () => {
    const result = manager.checkLocalText('色情视频');
    assert.strictEqual(result.hasViolation, true);
    assert.ok(result.matchedWords.includes('色情'));
  });

  it('should detect abusive language', () => {
    const result = manager.checkLocalText('你是个傻逼');
    assert.strictEqual(result.hasViolation, true);
    assert.ok(result.matchedWords.includes('傻逼'));
  });

  it('should pass clean content', () => {
    const result = manager.checkLocalText('今天天气真好，适合出去玩');
    assert.strictEqual(result.hasViolation, false);
    assert.strictEqual(result.matchedWords.length, 0);
  });

  it('should handle empty content', () => {
    const result = manager.checkLocalText('');
    assert.strictEqual(result.hasViolation, false);
  });

  it('should handle null content', () => {
    const result = manager.checkLocalText(null);
    assert.strictEqual(result.hasViolation, false);
  });

  it('should complete within 50ms', () => {
    const start = Date.now();
    for (let i = 0; i < 100; i++) {
      manager.checkLocalText('这是一个普通的测试内容，不包含任何敏感词汇');
    }
    const duration = Date.now() - start;
    const avgMs = duration / 100;
    assert.ok(avgMs < 50, `Average check time ${avgMs}ms exceeds 50ms target`);
  });

  it('should be case-insensitive', () => {
    const result = manager.checkLocalText('赌博');
    assert.strictEqual(result.hasViolation, true);
  });
});

describe('ContentSecurityManager - Word List Cache', () => {
  let manager;

  before(async () => {
    // Clear cache
    Object.keys(mockStorage).forEach(k => delete mockStorage[k]);

    manager = new ContentSecurityManager();
    await manager.init({ serverUrl: 'http://localhost:3000' });
  });

  it('should cache word list locally', () => {
    // After init, cache should be populated
    assert.ok(mockStorage['content_security_word_cache'] || manager.getWordCount() > 0);
  });

  it('should load from cache on subsequent init', async () => {
    // Pre-populate cache
    mockStorage['content_security_word_timestamp'] = Date.now();
    mockStorage['content_security_word_version'] = 'test_v1';
    mockStorage['content_security_word_cache'] = ['测试词1', '测试词2'];

    const manager2 = new ContentSecurityManager();
    await manager2.init({ serverUrl: 'http://localhost:3000' });

    assert.strictEqual(manager2.getWordCount(), 2);
  });

  it('should ignore expired cache', async () => {
    // Set expired cache
    mockStorage['content_security_word_timestamp'] = Date.now() - 25 * 60 * 60 * 1000; // 25 hours ago
    mockStorage['content_security_word_version'] = 'expired_v1';
    mockStorage['content_security_word_cache'] = ['过期词1'];

    const manager3 = new ContentSecurityManager();
    await manager3.init({ serverUrl: 'http://localhost:3000' });

    // Should use fallback, not expired cache
    assert.ok(manager3.getWordCount() > 1);
  });
});

describe('ContentSecurityManager - Full Text Check', () => {
  let manager;

  before(async () => {
    Object.keys(mockStorage).forEach(k => delete mockStorage[k]);
    manager = new ContentSecurityManager();
    await manager.init({ serverUrl: 'http://localhost:3000' });
  });

  it('should reject content with local violations', async () => {
    const result = await manager.fullTextCheck('test_openid', '赌博网站', ContentSecurityManager.SCENE.CHAT);
    assert.strictEqual(result.pass, false);
    assert.strictEqual(result.localViolation, true);
    assert.ok(result.errorMessage.length > 0);
  });

  it('should provide appropriate error message for nickname scene', async () => {
    const result = await manager.fullTextCheck('test_openid', '赌博', ContentSecurityManager.SCENE.NICKNAME);
    assert.ok(result.errorMessage.includes('昵称'));
  });

  it('should provide appropriate error message for chat scene', async () => {
    const result = await manager.fullTextCheck('test_openid', '色情内容', ContentSecurityManager.SCENE.CHAT);
    assert.ok(result.errorMessage.includes('违规'));
  });

  it('should provide appropriate error message for signature scene', async () => {
    const result = await manager.fullTextCheck('test_openid', '色情', ContentSecurityManager.SCENE.SIGNATURE);
    assert.ok(result.errorMessage.includes('违规'));
  });
});

describe('ContentSecurityManager - SCENE Constants', () => {
  it('should export SCENE constants', () => {
    assert.strictEqual(ContentSecurityManager.SCENE.NICKNAME, 1);
    assert.strictEqual(ContentSecurityManager.SCENE.CHAT, 2);
    assert.strictEqual(ContentSecurityManager.SCENE.SIGNATURE, 3);
    assert.strictEqual(ContentSecurityManager.SCENE.DESCRIPTION, 4);
  });

  it('should export REPORT_REASONS constants', () => {
    assert.strictEqual(ContentSecurityManager.REPORT_REASONS.POLITICS, 'politics');
    assert.strictEqual(ContentSecurityManager.REPORT_REASONS.PORNOGRAPHY, 'pornography');
    assert.strictEqual(ContentSecurityManager.REPORT_REASONS.GAMBLING, 'gambling');
    assert.strictEqual(ContentSecurityManager.REPORT_REASONS.OTHER, 'other');
  });
});

describe('ContentSecurityManager - Mute Status', () => {
  let manager;

  before(async () => {
    Object.keys(mockStorage).forEach(k => delete mockStorage[k]);
    manager = new ContentSecurityManager();
    await manager.init({ serverUrl: 'http://localhost:3000' });
  });

  it('should return not muted when server is unreachable', async () => {
    const status = await manager.getMuteStatus('nonexistent_user');
    assert.strictEqual(status.isMuted, false);
    assert.strictEqual(status.remainingMs, 0);
  });
});

describe('ContentSecurityManager - Submit Report', () => {
  let manager;

  before(async () => {
    Object.keys(mockStorage).forEach(k => delete mockStorage[k]);
    manager = new ContentSecurityManager();
    await manager.init({ serverUrl: 'http://localhost:3000' });
  });

  it('should handle report submission failure gracefully', async () => {
    const result = await manager.submitReport({
      contentId: 'test_123',
      targetUserId: 'target_456',
      contentType: 'chat',
      contentSummary: 'test',
      reporterId: 'reporter_789',
      reason: 'politics',
    });
    // Should not throw, returns failure object
    assert.strictEqual(result.success, false);
  });
});
