import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { publicationInternals } from './mindspace-publications.mjs';

test('normalizes safe long-page slugs', () => {
  assert.equal(publicationInternals.normalizeSlug('  product-intro-2026  '), 'product-intro-2026');
  assert.throws(
    () => publicationInternals.normalizeSlug('../private'),
    (error) => error.code === 'invalid_publish_input',
  );
  assert.throws(
    () => publicationInternals.normalizeSlug('中文地址'),
    (error) => error.code === 'invalid_publish_input',
  );
});

test('accepts all documented access modes', () => {
  for (const mode of [
    'public',
    'password',
    'private_link',
    'time_limited',
    'login_required',
    'owner_only',
  ]) {
    assert.equal(publicationInternals.normalizeAccessMode(mode), mode);
  }
  assert.throws(
    () => publicationInternals.normalizeAccessMode('team_only'),
    (error) => error.code === 'invalid_publish_input',
  );
});

test('hashes access passwords with a random salt', () => {
  const first = publicationInternals.hashPassword('Publish-Password-2026');
  const second = publicationInternals.hashPassword('Publish-Password-2026');
  assert.notEqual(first, second);
  assert.equal(publicationInternals.verifyPassword('Publish-Password-2026', first), true);
  assert.equal(publicationInternals.verifyPassword('wrong-password', first), false);
  assert.throws(
    () => publicationInternals.normalizePassword('short', true),
    (error) => error.code === 'invalid_publish_input',
  );
});

test('requires future expiry timestamps for time-limited pages', () => {
  const future = Date.now() + 60_000;
  assert.equal(publicationInternals.normalizeExpiresAt(future, true), future);
  assert.throws(
    () => publicationInternals.normalizeExpiresAt(Date.now() - 1, true),
    (error) => error.code === 'invalid_publish_input',
  );
});

test('classifies devices and strips referrer paths', () => {
  assert.equal(
    publicationInternals.deviceType('Mozilla/5.0 (iPhone; CPU iPhone OS 18_0) Mobile'),
    'mobile',
  );
  assert.equal(publicationInternals.deviceType('Googlebot/2.1'), 'bot');
  assert.equal(publicationInternals.deviceType('Mozilla/5.0 (Macintosh)'), 'desktop');
  assert.equal(
    publicationInternals.referrerHost('https://example.com/private/path?token=secret'),
    'example.com',
  );
  assert.equal(publicationInternals.referrerHost('not a url'), null);
});

test('warns for contact details and external links without silently hiding them', () => {
  const result = publicationInternals.scanContent(
    '联系 13812345678 或 hello@example.com，详情 https://example.com/path',
  );
  assert.equal(result.status, 'warned');
  assert.equal(result.riskLevel, 'medium');
  assert.equal(result.allowed, true);
  assert.deepEqual(
    result.findings.map((finding) => finding.type),
    ['phone', 'email', 'external_link'],
  );
  assert.doesNotMatch(result.findings[0].sampleMasked, /13812345678/);
});

test('deduplicates the same sensitive value repeated by summary and content', () => {
  const result = publicationInternals.scanContent(
    '摘要 qa@example.com\n正文 qa@example.com',
  );
  assert.equal(result.findings[0].occurrenceCount, 1);
});

test('blocks identity and bank-card numbers', () => {
  const result = publicationInternals.scanContent(
    '身份证 310101199001011234，银行卡 6222021234567890123',
  );
  assert.equal(result.status, 'blocked');
  assert.equal(result.riskLevel, 'high');
  assert.equal(result.allowed, false);
  assert.ok(result.findings.every((finding) => finding.blocking));
});

test('publicHomepageResponse summarizes public cards and total views', () => {
  const result = publicationInternals.publicHomepageResponse(
    {
      id: 'user-1',
      slug: 'john',
      username: 'john',
      display_name: 'John',
    },
    [
      {
        id: 'pub-1',
        page_id: 'page-1',
        title: '第一篇',
        summary: '摘要一',
        template_id: 'report',
        public_url: '/u/john/pages/one',
        url_slug: 'one',
        view_count: 7,
        published_at: 100,
      },
      {
        id: 'pub-2',
        page_id: 'page-2',
        title: '第二篇',
        summary: '摘要二',
        template_id: 'editorial',
        public_url: '/u/john/pages/two',
        url_slug: 'two',
        view_count: 3,
        published_at: 200,
      },
    ],
  );
  assert.equal(result.owner.displayName, 'John');
  assert.equal(result.totalViews, 10);
  assert.equal(result.pageCount, 2);
  assert.equal(result.pages[0].publicUrl, '/u/john/pages/one');
});

test('buildPublicationThumbnailFallback points private resources at the public page thumbnail', () => {
  assert.equal(
    publicationInternals.buildPublicationThumbnailFallback('john', 'page-037d497f'),
    '/u/john/pages/page-037d497f.thumbnail.png',
  );
  assert.equal(
    publicationInternals.buildPublicationThumbnailFallback('木子', 'demo-page'),
    '/u/%E6%9C%A8%E5%AD%90/pages/demo-page.thumbnail.png',
  );
});

test('prepareHtmlPublishContent inlines owned private image assets before scanning', async () => {
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'mindspace-publish-'));
  const storageKey = 'users/user-1/assets/asset-1/current.png';
  await fs.mkdir(path.dirname(path.join(root, storageKey)), { recursive: true });
  await fs.writeFile(path.join(root, storageKey), Buffer.from('png-bytes'));

  const pool = {
    async query(sql, params) {
      assert.match(sql, /h5_assets/);
      assert.equal(params[0], 'user-1');
      assert.deepEqual(params[1], ['asset-1']);
      return [
        [
          {
            id: 'asset-1',
            mime_type: 'image/png',
            storage_key: storageKey,
          },
        ],
      ];
    },
  };

  try {
    const html = '<!doctype html><img src="/api/mindspace/v1/assets/asset-1/download?inline=1&v=1">';
    const prepared = await publicationInternals.prepareHtmlPublishContent({
      pool,
      userId: 'user-1',
      html,
      ownerSlug: 'john',
      urlSlug: 'korean-daily-look',
      absoluteStoragePath: (key) => path.join(root, key),
    });

    assert.match(prepared, /src="data:image\/png;base64,/);
    assert.doesNotMatch(prepared, /\/api\/mindspace\/v1\/assets\//);
    const scan = publicationInternals.scanContent(prepared, { format: 'html' });
    assert.equal(
      scan.findings.some((finding) => finding.type === 'private_resource_reference'),
      false,
    );
  } finally {
    await fs.rm(root, { recursive: true, force: true });
  }
});
