import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import test from 'node:test';

import {
  loadPathAliasManifest,
  parsePathAliasManifest,
  resolveChangelogPath,
  transitionAliasedFrontendState,
  validatePathAliases,
} from '../scripts/changelog-path-aliases.mjs';
import { parseFrontmatter } from '../scripts/validate-changelog-frontmatter.mjs';

const OLD_5216 = 'changelogs-v2/2026-07/24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-前端待处理-管理后台.md';
const CURRENT_5216 = 'changelogs-v2/2026-07/24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-管理后台.md';

function document({ canonicalPath = '', status = 'claimed', owner = 'frontend-team', frontendRef = '' } = {}) {
  const canonicalLine = canonicalPath ? `canonical_path: "${canonicalPath}"\n` : '';
  return `---
schema: "hl-changelog/v2"
ticket: "5216"
title: "路径兼容测试"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "${status}"
frontend_owner: "${owner}"
frontend_ref: "${frontendRef}"
target_release: ""
verified_at: ""
${canonicalLine}updated_at: "2026-07-26"
base: "dev-v3"
---

# 路径兼容测试

## 变更接口

- 无业务接口变化。

## 验证证据

- 自动化测试。
`;
}

function createFixture() {
  const root = mkdtempSync(path.join(tmpdir(), 'hl-changelog-alias-'));
  const manifest = {
    schema: 'hl-changelog-path-aliases/v1',
    aliases: [{
      ticket: '5216',
      alias: OLD_5216,
      canonical: CURRENT_5216,
      reason: '测试旧路径兼容',
    }],
  };
  writeFileSync(path.join(root, 'changelog-path-aliases.json'), JSON.stringify(manifest, null, 2));
  for (const repoPath of [OLD_5216, CURRENT_5216]) {
    const absolute = path.join(root, ...repoPath.split('/'));
    mkdirSync(path.dirname(absolute), { recursive: true });
    writeFileSync(absolute, document({
      canonicalPath: repoPath === OLD_5216 ? CURRENT_5216 : '',
    }));
  }
  return root;
}

test('repository manifest has no dangling aliases after illegal legacy paths are removed', () => {
  const manifest = loadPathAliasManifest();
  assert.equal(resolveChangelogPath(OLD_5216, manifest), OLD_5216);
  assert.equal(resolveChangelogPath(CURRENT_5216, manifest), CURRENT_5216);
  assert.deepEqual(validatePathAliases().errors, []);
});

test('rejects unsafe paths, duplicate aliases and alias chains', () => {
  assert.throws(() => parsePathAliasManifest(JSON.stringify({
    schema: 'hl-changelog-path-aliases/v1',
    aliases: [{
      ticket: '5216',
      alias: '../outside.md',
      canonical: CURRENT_5216,
      reason: 'unsafe',
    }],
  })), /POSIX|规范路径/);
  assert.throws(() => parsePathAliasManifest(JSON.stringify({
    schema: 'hl-changelog-path-aliases/v1',
    aliases: [
      { ticket: '5216', alias: OLD_5216, canonical: CURRENT_5216, reason: 'one' },
      { ticket: '5216', alias: OLD_5216, canonical: CURRENT_5216, reason: 'two' },
    ],
  })), /重复/);
});

test('frontend status transition updates alias group once and is idempotent', () => {
  const root = createFixture();
  try {
    const first = transitionAliasedFrontendState(root, OLD_5216, {
      status: 'implemented',
      owner: 'frontend-team',
      frontendRef: 'mmg/hl-ui@abc1234',
    }, {
      now: new Date('2026-07-26T04:00:00Z'),
      write: true,
    });
    assert.deepEqual(new Set(first.changedPaths), new Set([OLD_5216, CURRENT_5216]));
    assert.deepEqual(validatePathAliases(root).errors, []);

    for (const repoPath of [OLD_5216, CURRENT_5216]) {
      const text = readFileSync(path.join(root, ...repoPath.split('/')), 'utf8');
      const { metadata } = parseFrontmatter(text);
      assert.equal(metadata.frontend_status, 'implemented');
      assert.equal(metadata.frontend_owner, 'frontend-team');
      assert.equal(metadata.frontend_ref, 'mmg/hl-ui@abc1234');
    }

    const second = transitionAliasedFrontendState(root, OLD_5216, {
      status: 'implemented',
      owner: 'frontend-team',
      frontendRef: 'mmg/hl-ui@abc1234',
    }, {
      now: new Date('2026-07-26T04:00:00Z'),
      write: true,
    });
    assert.deepEqual(second.changedPaths, []);
  } finally {
    rmSync(root, { recursive: true, force: true });
  }
});
