#!/usr/bin/env node

import { readFileSync } from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';

import {
  controlledRootForPath,
  diffFromOptions,
  parseArguments,
  parseNameStatusZ,
} from './validate-changelog-filenames.mjs';

const FRONTEND_STATUSES = new Set([
  'not_required',
  'pending',
  'claimed',
  'implemented',
  'released',
  'verified',
]);
const BACKEND_STATUSES = new Set(['pending', 'tested', 'deployed', 'not_required']);
const GATEWAY_STATUSES = new Set(['pending', 'verified', 'not_required']);
const CONSUMERS = new Set(['admin', 'mp', 'internal', 'multiple']);
const CHANGE_TYPES = new Set(['新增接口', '修改接口', '删除接口', '修复', '前端缺陷', '前端优化', '前端修复']);
// 接口契约类条目:后端必须已部署测试服并实测(E_BACKEND_PENDING 硬门禁)才允许发布
const API_CHANGE_TYPES = new Set(['新增接口', '修改接口', '删除接口']);
const REQUIRED_KEYS = [
  'schema',
  'ticket',
  'title',
  'consumer',
  'change_type',
  'backend_status',
  'gateway_status',
  'frontend_status',
  'frontend_owner',
  'frontend_ref',
  'target_release',
  'verified_at',
  'updated_at',
  'base',
];

function ruleError(code, file, message) {
  return { code, path: file, message };
}

function isIsoDate(value) {
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value ?? '');
  if (!match) {
    return false;
  }
  const year = Number(match[1]);
  const month = Number(match[2]);
  const day = Number(match[3]);
  const parsed = new Date(Date.UTC(year, month - 1, day));
  return parsed.getUTCFullYear() === year
    && parsed.getUTCMonth() === month - 1
    && parsed.getUTCDate() === day;
}

function isIsoDateOrTime(value) {
  if (isIsoDate(value)) {
    return true;
  }
  return /^\d{4}-\d{2}-\d{2}T/.test(value ?? '')
    && Number.isFinite(Date.parse(value));
}

export function parseFrontmatter(text) {
  const value = String(text ?? '').replaceAll('\r\n', '\n');
  if (!value.startsWith('---\n')) {
    return { metadata: undefined, body: value };
  }
  const end = value.indexOf('\n---\n', 4);
  if (end < 0) {
    return { metadata: undefined, body: value };
  }
  const metadata = {};
  for (const line of value.slice(4, end).split('\n')) {
    const separator = line.indexOf(':');
    if (separator < 0) {
      continue;
    }
    const key = line.slice(0, separator).trim();
    let fieldValue = line.slice(separator + 1).trim();
    if (
      (fieldValue.startsWith('"') && fieldValue.endsWith('"'))
      || (fieldValue.startsWith("'") && fieldValue.endsWith("'"))
    ) {
      fieldValue = fieldValue.slice(1, -1);
    }
    metadata[key] = fieldValue;
  }
  return { metadata, body: value.slice(end + 5) };
}

export function validateFrontendState(metadata) {
  const errors = [];
  const status = metadata.frontend_status;
  const owner = metadata.frontend_owner?.trim() ?? '';
  const reference = metadata.frontend_ref?.trim() ?? '';
  const release = metadata.target_release?.trim() ?? '';
  const verifiedAt = metadata.verified_at?.trim() ?? '';
  if (!FRONTEND_STATUSES.has(status)) {
    return [`frontend_status 非法: ${status || '(空)'}`];
  }
  if (['claimed', 'implemented', 'released', 'verified'].includes(status) && !owner) {
    errors.push(`${status} 必须填写 frontend_owner`);
  }
  if (['implemented', 'released', 'verified'].includes(status) && !reference) {
    errors.push(`${status} 必须填写 frontend_ref`);
  }
  if (['released', 'verified'].includes(status) && !release) {
    errors.push(`${status} 必须填写 target_release`);
  }
  if (status === 'verified' && !verifiedAt) {
    errors.push('verified 必须填写 verified_at');
  }
  if (verifiedAt && !isIsoDateOrTime(verifiedAt)) {
    errors.push('verified_at 必须是 ISO 日期或时间');
  }
  if (status === 'not_required' && [owner, reference, release, verifiedAt].some(Boolean)) {
    errors.push('not_required 不得保留前端负责人、引用、版本或验证时间');
  }
  return errors;
}

export function validateFrontendTransition(current, target, reason = '') {
  if (!FRONTEND_STATUSES.has(current) || !FRONTEND_STATUSES.has(target)) {
    return ['frontend_status 非法'];
  }
  if (current === target) {
    return [];
  }
  if (current === 'not_required' || target === 'not_required') {
    return reason.trim() ? [] : ['涉及 not_required 的迁移必须填写原因'];
  }
  const order = ['pending', 'claimed', 'implemented', 'released', 'verified'];
  const currentIndex = order.indexOf(current);
  const targetIndex = order.indexOf(target);
  if (targetIndex === currentIndex + 1) {
    return [];
  }
  if (targetIndex < currentIndex) {
    return reason.trim() ? [] : ['状态回退必须填写原因'];
  }
  return [`禁止跨级迁移: ${current} -> ${target}`];
}

export function validateV2Document(file, text, { requireV2 = false } = {}) {
  const { metadata, body } = parseFrontmatter(text);
  if (!metadata) {
    return requireV2 ? [ruleError('E_FRONTMATTER', file, '新增 changelog 缺少 YAML Front Matter')] : [];
  }
  if (metadata.schema !== 'hl-changelog/v2') {
    return requireV2
      ? [ruleError('E_SCHEMA', file, `新增 changelog 必须使用 hl-changelog/v2，当前为 ${metadata.schema || '(空)'}`)]
      : [];
  }
  const errors = [];
  for (const key of REQUIRED_KEYS) {
    if (!(key in metadata)) {
      errors.push(ruleError('E_REQUIRED', file, `frontmatter 缺少 ${key}`));
    }
  }
  for (const key of ['ticket', 'title', 'consumer', 'change_type', 'backend_status', 'gateway_status', 'frontend_status', 'updated_at', 'base']) {
    if (!metadata[key]?.trim()) {
      errors.push(ruleError('E_REQUIRED', file, `${key} 不能为空`));
    }
  }
  if (!CHANGE_TYPES.has(metadata.change_type)) {
    errors.push(ruleError('E_CHANGE_TYPE', file, `change_type 非法: ${metadata.change_type || '(空)'}`));
  }
  if (!CONSUMERS.has(metadata.consumer)) {
    errors.push(ruleError('E_CONSUMER', file, `consumer 非法: ${metadata.consumer || '(空)'}`));
  }
  if (!BACKEND_STATUSES.has(metadata.backend_status)) {
    errors.push(ruleError('E_BACKEND_STATUS', file, `backend_status 非法: ${metadata.backend_status || '(空)'}`));
  }
  if (!GATEWAY_STATUSES.has(metadata.gateway_status)) {
    errors.push(ruleError('E_GATEWAY_STATUS', file, `gateway_status 非法: ${metadata.gateway_status || '(空)'}`));
  }
  if (API_CHANGE_TYPES.has(metadata.change_type) && metadata.backend_status === 'not_required') {
    errors.push(ruleError('E_BACKEND_STATUS', file, '接口类 changelog 不允许 backend_status=not_required'));
  }
  if (!['deployed', 'not_required'].includes(metadata.backend_status)) {
    errors.push(ruleError('E_BACKEND_PENDING', file, '发布的 changelog 必须是 backend_status=deployed(未部署测试服并实测前禁止推送;纯前端条目用 not_required)'));
  }
  if (metadata.gateway_status === 'pending') {
    errors.push(ruleError('E_GATEWAY_PENDING', file, '发布的 changelog 不能保留 gateway_status=pending'));
  }
  for (const message of validateFrontendState(metadata)) {
    errors.push(ruleError('E_FRONTEND_STATE', file, message));
  }
  if (metadata.consumer === 'internal' && metadata.frontend_status !== 'not_required') {
    errors.push(ruleError('E_FRONTEND_STATE', file, 'internal consumer 必须使用 frontend_status=not_required'));
  }
  if (!isIsoDate(metadata.updated_at)) {
    errors.push(ruleError('E_UPDATED_AT', file, 'updated_at 必须是真实的 YYYY-MM-DD 日期'));
  }
  const filename = path.posix.basename(file);
  const issue = /^\d{2}_([1-9]\d*)_/.exec(filename)?.[1];
  if (issue && metadata.ticket !== issue) {
    errors.push(ruleError('E_TICKET_MISMATCH', file, `ticket=${metadata.ticket} 与文件名 Issue=${issue} 不一致`));
  }
  const filenameType = /-(新增接口|修改接口|删除接口|修复|前端缺陷|前端优化|前端修复)-(?:管理后台|小程序端)\.md$/.exec(filename)?.[1];
  if (filenameType && metadata.change_type !== filenameType) {
    errors.push(ruleError('E_TYPE_MISMATCH', file, `change_type=${metadata.change_type} 与文件名=${filenameType} 不一致`));
  }
  // 单花括号是 REST 路径参数惯例({orderId}),只拦真正的模板残留:双花括号、TODO、待补充
  if (/\{\{[^{}\n]+\}\}|\bTODO\b|待补充/i.test(body)) {
    errors.push(ruleError('E_PLACEHOLDER', file, '正文仍有 TODO、待补充或模板占位符'));
  }
  // 接口类条目必须有接口清单章节与验证章节;修复/前端类条目结构自由,不强制
  if (API_CHANGE_TYPES.has(metadata.change_type)) {
    const hasApiSection = /^##[^\n]*(变更接口|变更清单|变更内容|接口详情|变更点|接口变化|行为变化)/m.test(body);
    const hasEvidenceSection = /^##[^\n]*(验证|测试|复现|证据)/m.test(body);
    if (!hasApiSection || !hasEvidenceSection) {
      errors.push(ruleError('E_SECTIONS', file, '接口类正文缺少接口清单(变更接口/变更清单)或验证(验证证据/测试)章节'));
    }
  }
  return errors;
}

export function collectChangedDocuments(records) {
  return records
    .filter(({ status }) => status !== 'D')
    .map((record) => ({
      path: record.targetPath,
      isNew: record.status === 'A' || /^C\d{1,3}$/.test(record.status) || /^R\d{1,3}$/.test(record.status),
    }))
    .filter(({ path: file }) => controlledRootForPath(file) && file.endsWith('.md'));
}

export function runFrontmatterValidation(records, root = process.cwd()) {
  const documents = collectChangedDocuments(records);
  const errors = [];
  for (const document of documents) {
    let text;
    try {
      text = readFileSync(path.join(root, ...document.path.split('/')), 'utf8');
    } catch (error) {
      errors.push(ruleError('E_READ', document.path, `无法读取文件: ${error.message}`));
      continue;
    }
    errors.push(...validateV2Document(document.path, text, { requireV2: document.isNew }));
  }
  return { checkedCount: documents.length, errors };
}

export function main(argv = process.argv.slice(2)) {
  try {
    const options = parseArguments(argv);
    const records = parseNameStatusZ(diffFromOptions(options));
    const result = runFrontmatterValidation(records);
    if (result.errors.length > 0) {
      for (const error of result.errors) {
        console.error(`[${error.code}] ${error.path}: ${error.message}`);
      }
      console.error(`FAIL: ${result.errors.length} frontmatter error(s) in ${result.checkedCount} changelog file(s).`);
      return 1;
    }
    console.log(`PASS: validated frontmatter for ${result.checkedCount} changed changelog file(s).`);
    return 0;
  } catch (error) {
    console.error(`ERROR: ${error instanceof Error ? error.message : String(error)}`);
    return 2;
  }
}

const isCli = process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url;
if (isCli) {
  process.exitCode = main();
}
