import dns from 'node:dns';
import { buildDkimRecord } from './dkim.js';

const resolver = new dns.promises.Resolver();
resolver.setServers(
  String(process.env.DNS_RESOLVERS || '1.1.1.1,8.8.8.8')
    .split(',')
    .map((server) => server.trim())
    .filter(Boolean)
);

export async function buildDnsGuide(domain) {
  const live = await readLiveDns(domain);
  const requiredSpf = buildRequiredSpfMechanisms(domain);
  const spf = mergeSpfRecords(live.rootTxt.filter(isSpfRecord), requiredSpf);
  const dmarc = mergeDmarcRecord(live.dmarcTxt.find(isDmarcRecord), domain);
  const verificationValue = `mailhub-verification=${domain.verificationToken}`;
  const dkimValue = buildDkimRecord(domain.dkimPublic);
  const records = [
    {
      key: 'verification',
      label: '域名验证',
      host: `_mailhub.${domain.domain}`,
      type: 'TXT',
      value: verificationValue,
      current: live.verificationTxt,
      status: containsTxt(live.verificationTxt, verificationValue) ? 'ok' : 'missing'
    },
    {
      key: 'dkim',
      label: 'DKIM',
      host: `${domain.selector}._domainkey.${domain.domain}`,
      type: 'TXT',
      value: dkimValue,
      current: live.dkimTxt,
      status: containsTxt(live.dkimTxt, dkimValue) ? 'ok' : 'missing'
    },
    {
      key: 'spf',
      label: 'SPF',
      host: domain.domain,
      type: 'TXT',
      value: spf.recommended,
      status: spf.ok ? 'ok' : 'warn',
      current: spf.current,
      warnings: spf.warnings
    },
    {
      key: 'dmarc',
      label: 'DMARC',
      host: `_dmarc.${domain.domain}`,
      type: 'TXT',
      value: dmarc.recommended,
      status: dmarc.ok ? 'ok' : 'warn',
      current: dmarc.current,
      warnings: dmarc.warnings
    },
    {
      key: 'sender-a',
      label: '平台发信主机 A',
      host: domain.senderHost,
      type: 'A',
      value: domain.sendingIp,
      managed: true,
      status: live.senderA.includes(domain.sendingIp) ? 'ok' : 'warn',
      current: live.senderA.join(', '),
      warnings: live.senderA.includes(domain.sendingIp)
        ? []
        : [`${domain.senderHost} 当前未解析到 ${domain.sendingIp}，这是平台发信主机，请联系管理员检查。`]
    }
  ];

  const okKeys = new Set(records.filter((record) => record.status === 'ok').map((record) => record.key));
  const verified = okKeys.has('verification') && okKeys.has('dkim') && okKeys.has('spf') && okKeys.has('dmarc');

  return {
    checkedAt: new Date().toISOString(),
    verified,
    records,
    live,
    requiredSpf,
    optionalRecords: buildOptionalRecords(domain),
    warnings: collectWarnings(records, spf, dmarc, live)
  };
}

async function readLiveDns(domain) {
  const [
    rootTxt,
    verificationTxt,
    dkimTxt,
    dmarcTxt,
    senderA
  ] = await Promise.all([
    resolveTxt(domain.domain),
    resolveTxt(`_mailhub.${domain.domain}`),
    resolveTxt(`${domain.selector}._domainkey.${domain.domain}`),
    resolveTxt(`_dmarc.${domain.domain}`),
    resolve4(domain.senderHost)
  ]);
  return {
    rootTxt,
    verificationTxt,
    dkimTxt,
    dmarcTxt,
    senderA
  };
}

export async function buildSystemDnsChecks(settings = {}) {
  const mailHostname = String(settings.mailHostname || '').trim();
  const sendingIp = String(settings.sendingIp || '').trim();
  const ptr = sendingIp ? await resolvePtr(sendingIp) : [];
  const ok = Boolean(mailHostname && sendingIp && ptr.some((value) => hostEquals(value, mailHostname)));

  return {
    checkedAt: new Date().toISOString(),
    ptr: {
      key: 'ptr',
      label: '发信 IP PTR',
      host: sendingIp,
      type: 'PTR',
      value: mailHostname,
      status: ok ? 'ok' : (sendingIp ? 'warn' : 'missing'),
      current: ptr,
      warnings: ok
        ? []
        : ['PTR 需要在云服务器或 IP 服务商控制台设置，普通 DNS 控制台通常不能修改。']
    }
  };
}

async function resolveTxt(name) {
  try {
    const rows = await resolver.resolveTxt(name);
    return rows.map((parts) => parts.join(''));
  } catch (error) {
    if (['ENODATA', 'ENOTFOUND', 'SERVFAIL', 'ETIMEOUT'].includes(error.code)) return [];
    return [`DNS lookup failed: ${error.code || error.message}`];
  }
}

async function resolve4(name) {
  try {
    return await resolver.resolve4(name);
  } catch {
    return [];
  }
}

async function resolvePtr(ip) {
  try {
    return await resolver.reverse(ip);
  } catch {
    return [];
  }
}

function containsTxt(records, expected) {
  return records.some((record) => normalizeTxt(record) === normalizeTxt(expected));
}

function normalizeTxt(value) {
  return String(value).replace(/\s+/g, ' ').trim();
}

function hostEquals(left, right) {
  return String(left || '').trim().replace(/\.$/, '').toLowerCase()
    === String(right || '').trim().replace(/\.$/, '').toLowerCase();
}

function isSpfRecord(value) {
  return /^v=spf1(?:\s|$)/i.test(value.trim());
}

function isDmarcRecord(value) {
  return /^v=DMARC1(?:;|\s|$)/i.test(value.trim());
}

function buildRequiredSpfMechanisms(domain) {
  const mechanisms = [];
  if (domain.sendingIp) mechanisms.push(`ip4:${domain.sendingIp}`);
  if (domain.senderHost) mechanisms.push(`a:${domain.senderHost}`);
  mechanisms.push(...splitMechanisms(domain.spfExtra));
  return uniqueMechanisms(mechanisms);
}

function splitMechanisms(value) {
  return String(value || '')
    .split(/[\s,]+/)
    .map((item) => item.trim())
    .filter(Boolean);
}

export function mergeSpfRecords(existingRecords, requiredMechanisms) {
  const warnings = [];
  const current = existingRecords.map(normalizeTxt);
  if (current.length > 1) {
    warnings.push('当前域名存在多条 SPF TXT，收件方会判定 SPF permerror；需要合并为一条。');
  }
  if (current.length === 0) {
    const recommended = `v=spf1 ${requiredMechanisms.join(' ')} ~all`.replace(/\s+/g, ' ').trim();
    warnings.push('当前没有 SPF 记录。');
    return {
      current,
      recommended,
      ok: false,
      warnings: withLookupWarning(warnings, recommended)
    };
  }

  const parsed = current.map(parseSpf);
  const mechanisms = [];
  for (const record of parsed) mechanisms.push(...record.mechanisms);
  mechanisms.push(...requiredMechanisms);

  const all = parsed.find((record) => record.all === '-all')?.all
    || parsed.find((record) => record.all === '~all')?.all
    || parsed.find((record) => record.all === '?all')?.all
    || '~all';
  const recommended = `v=spf1 ${uniqueMechanisms(mechanisms).join(' ')} ${all}`
    .replace(/\s+/g, ' ')
    .trim();
  const ok = current.length === 1
    && normalizeTxt(current[0]) === recommended
    && requiredMechanisms.every((mechanism) => hasMechanism(current[0], mechanism));

  return {
    current,
    recommended,
    ok,
    warnings: withLookupWarning(warnings, recommended)
  };
}

function parseSpf(record) {
  const tokens = normalizeTxt(record).split(/\s+/).slice(1);
  const mechanisms = [];
  let all = '~all';
  for (const token of tokens) {
    if (/^[+\-~?]?all$/i.test(token)) {
      all = token;
    } else if (token) {
      mechanisms.push(token);
    }
  }
  return { mechanisms, all };
}

function uniqueMechanisms(items) {
  const seen = new Set();
  const output = [];
  for (const item of items) {
    const normalized = normalizeMechanism(item);
    if (!normalized || seen.has(normalized)) continue;
    seen.add(normalized);
    output.push(item.replace(/^\+/, ''));
  }
  return output;
}

function normalizeMechanism(item) {
  return String(item || '').trim().replace(/^\+/, '').toLowerCase();
}

function hasMechanism(record, mechanism) {
  const normalized = normalizeMechanism(mechanism);
  return parseSpf(record).mechanisms.some((item) => normalizeMechanism(item) === normalized);
}

function withLookupWarning(warnings, spf) {
  const lookupCount = (spf.match(/\b(include|a|mx|ptr|exists|redirect)[=:]?/g) || []).length;
  if (lookupCount > 10) {
    return [...warnings, `SPF DNS 查询项约为 ${lookupCount} 个，超过 10 个会失败；建议减少 include 或改用专用子域。`];
  }
  if (lookupCount >= 8) {
    return [...warnings, `SPF DNS 查询项约为 ${lookupCount} 个，接近 10 个上限。`];
  }
  return warnings;
}

export function mergeDmarcRecord(existingRecord, domain) {
  const current = existingRecord ? normalizeTxt(existingRecord) : '';
  const warnings = [];
  const tags = parseDmarc(current);
  if (!current) warnings.push('当前没有 DMARC 记录。');
  tags.set('v', 'DMARC1');
  tags.set('p', domain.dmarcPolicy || tags.get('p') || 'none');
  tags.set('adkim', tags.get('adkim') || 's');
  tags.set('aspf', tags.get('aspf') || 's');
  tags.set('pct', tags.get('pct') || '100');
  const rua = domain.dmarcRua || tags.get('rua') || `mailto:dmarc@${domain.domain}`;
  if (rua) tags.set('rua', rua);
  const order = ['v', 'p', 'rua', 'ruf', 'adkim', 'aspf', 'pct', 'fo'];
  const recommended = [
    ...order.filter((key) => tags.has(key)).map((key) => `${key}=${tags.get(key)}`),
    ...[...tags.entries()]
      .filter(([key]) => !order.includes(key))
      .map(([key, value]) => `${key}=${value}`)
  ].join('; ');
  return {
    current: current ? [current] : [],
    recommended,
    ok: current === recommended,
    warnings
  };
}

function parseDmarc(record) {
  const tags = new Map();
  for (const part of String(record || '').split(';')) {
    const [key, ...rest] = part.trim().split('=');
    if (!key || !rest.length) continue;
    tags.set(key.toLowerCase(), rest.join('=').trim());
  }
  return tags;
}

function buildOptionalRecords(domain) {
  return [
    {
      label: 'TLS-RPT',
      host: `_smtp._tls.${domain.domain}`,
      type: 'TXT',
      value: `v=TLSRPTv1; rua=mailto:tlsrpt@${domain.domain}`
    },
    {
      label: 'MTA-STS',
      host: `_mta-sts.${domain.domain}`,
      type: 'TXT',
      value: 'v=STSv1; id=2026070701'
    },
    {
      label: 'BIMI',
      host: `default._bimi.${domain.domain}`,
      type: 'TXT',
      value: `v=BIMI1; l=https://${domain.domain}/bimi.svg`
    }
  ];
}

function collectWarnings(records, spf, dmarc, live) {
  const warnings = [
    ...records.flatMap((record) => record.warnings || []),
    ...spf.warnings,
    ...dmarc.warnings
  ];
  if (live.rootTxt.filter(isSpfRecord).length > 1) {
    warnings.push('SPF 必须只有一条 TXT；不要新增第二条 v=spf1。');
  }
  return [...new Set(warnings)];
}
