import "server-only";

import { Resolver } from "node:dns/promises";
import { domainToASCII } from "node:url";

const OFFICIAL_MAIL_MX = "mx1.officialsite.kr";
const OFFICIAL_MAIL_SPF_INCLUDE = "include:mx1.officialsite.kr";
const DNS_TIMEOUT_MS = 5_000;
const publicDnsResolver = new Resolver();

publicDnsResolver.setServers(["1.1.1.1", "8.8.8.8"]);

export type DomainMailDiagnosticStatus = "pass" | "warning" | "fail";

export type DomainMailDiagnosticRecord = {
  currentValues: string[];
  expectedValue: string;
  host: string;
  key: "mx" | "spf" | "dkim" | "dmarc";
  label: string;
  message: string;
  status: DomainMailDiagnosticStatus;
};

export type DomainMailDiagnosticResult = {
  checkedAt: string;
  domain: string;
  records: DomainMailDiagnosticRecord[];
  score: number;
  status: "ready" | "partial" | "not-ready";
  summary: string;
};

type DnsLookupResult = {
  errorCode: string | null;
  values: string[];
};

function trimTrailingDot(value: string) {
  return value.trim().replace(/\.$/, "").toLowerCase();
}

export function normalizeDiagnosticDomain(value: string) {
  const input = value.trim();

  if (!input || input.length > 512) {
    throw new Error("invalid-domain");
  }

  let hostname = input;

  try {
    const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(input)
      ? input
      : `https://${input}`;
    hostname = new URL(withProtocol).hostname;
  } catch {
    throw new Error("invalid-domain");
  }

  const ascii = domainToASCII(hostname.replace(/\.$/, "").toLowerCase())
    .replace(/^www\./, "");

  if (
    !ascii ||
    ascii.length > 253 ||
    ascii === "localhost" ||
    !ascii.includes(".") ||
    !/^[a-z0-9.-]+$/.test(ascii) ||
    ascii.split(".").some((label) =>
      !label ||
      label.length > 63 ||
      label.startsWith("-") ||
      label.endsWith("-")
    )
  ) {
    throw new Error("invalid-domain");
  }

  return ascii;
}

async function withDnsTimeout<T>(operation: Promise<T>) {
  let timeout: ReturnType<typeof setTimeout> | null = null;

  try {
    return await Promise.race([
      operation,
      new Promise<T>((_, reject) => {
        timeout = setTimeout(() => reject(Object.assign(new Error("dns-timeout"), { code: "ETIMEOUT" })), DNS_TIMEOUT_MS);
      }),
    ]);
  } finally {
    if (timeout) clearTimeout(timeout);
  }
}

function dnsErrorCode(error: unknown) {
  if (error && typeof error === "object" && "code" in error) {
    return String(error.code || "DNS_ERROR");
  }

  return "DNS_ERROR";
}

async function lookupTxt(hostname: string): Promise<DnsLookupResult> {
  try {
    const rows = await withDnsTimeout(publicDnsResolver.resolveTxt(hostname));
    return {
      errorCode: null,
      values: rows.map((chunks) => chunks.join("").trim()).filter(Boolean),
    };
  } catch (error) {
    return { errorCode: dnsErrorCode(error), values: [] };
  }
}

async function lookupMx(hostname: string): Promise<DnsLookupResult> {
  try {
    const rows = await withDnsTimeout(publicDnsResolver.resolveMx(hostname));
    return {
      errorCode: null,
      values: rows
        .sort((left, right) => left.priority - right.priority)
        .map((row) => `${row.priority} ${trimTrailingDot(row.exchange)}`),
    };
  } catch (error) {
    return { errorCode: dnsErrorCode(error), values: [] };
  }
}

function missingMessage(errorCode: string | null) {
  if (errorCode === "ESERVFAIL") {
    return "DNS 서버가 응답을 처리하지 못했습니다. 긴 TXT 값이나 DNS 제공업체 설정을 확인하세요.";
  }

  if (errorCode === "ETIMEOUT") {
    return "DNS 조회 시간이 초과되었습니다. 잠시 후 다시 확인하세요.";
  }

  return "공개 DNS에서 레코드를 찾지 못했습니다.";
}

function recordScore(record: DomainMailDiagnosticRecord) {
  if (record.status === "pass") return 25;
  if (record.status === "warning") return 10;
  return 0;
}

export async function diagnoseDomainMail(input: string): Promise<DomainMailDiagnosticResult> {
  const domain = normalizeDiagnosticDomain(input);
  const [mx, rootTxt, dkimTxt, dmarcTxt] = await Promise.all([
    lookupMx(domain),
    lookupTxt(domain),
    lookupTxt(`dkim._domainkey.${domain}`),
    lookupTxt(`_dmarc.${domain}`),
  ]);
  const spfValues = rootTxt.values.filter((value) => /^v=spf1\b/i.test(value));
  const dkimValues = dkimTxt.values.filter((value) => /^v=dkim1\b/i.test(value));
  const dmarcValues = dmarcTxt.values.filter((value) => /^v=dmarc1\b/i.test(value));
  const officialMx = mx.values.some((value) => trimTrailingDot(value.replace(/^\d+\s+/, "")) === OFFICIAL_MAIL_MX);
  const officialSpf = spfValues.some((value) => value.toLowerCase().includes(OFFICIAL_MAIL_SPF_INCLUDE));
  const hasOtherMailProvider = mx.values.length > 0 && !officialMx;
  const records: DomainMailDiagnosticRecord[] = [
    {
      currentValues: mx.values,
      expectedValue: `MX @ → ${OFFICIAL_MAIL_MX}. / 우선순위 10`,
      host: "@",
      key: "mx",
      label: "MX",
      message: officialMx
        ? "오피셜메일 수신 서버가 연결되어 있습니다."
        : mx.values.length > 0
          ? "다른 메일 수신 서버가 연결되어 있습니다. 전환 전에 기존 메일 운영 여부를 확인하세요."
          : missingMessage(mx.errorCode),
      status: officialMx ? "pass" : mx.values.length > 0 ? "warning" : "fail",
    },
    {
      currentValues: spfValues,
      expectedValue: `TXT @ → v=spf1 ${OFFICIAL_MAIL_SPF_INCLUDE} -all`,
      host: "@",
      key: "spf",
      label: "SPF",
      message: officialSpf
        ? "오피셜메일 발송 서버가 SPF 정책에 포함되어 있습니다."
        : spfValues.length > 1
          ? "SPF 레코드가 여러 개입니다. 하나의 TXT 정책으로 합쳐야 합니다."
          : spfValues.length === 1
            ? "SPF는 있지만 오피셜메일 발송 서버가 포함되지 않았습니다."
            : missingMessage(rootTxt.errorCode),
      status: officialSpf ? "pass" : spfValues.length > 0 ? "warning" : "fail",
    },
    {
      currentValues: dkimValues,
      expectedValue: "오피셜메일 연결 시 TXT dkim._domainkey → 오피셜메일에서 발급한 DKIM 공개키",
      host: "dkim._domainkey",
      key: "dkim",
      label: "DKIM",
      message: dkimValues.length > 0
        ? "DKIM 공개키가 조회됩니다. 실제 서명 일치 여부는 발송 테스트에서 최종 확인하세요."
        : dkimTxt.errorCode === "ESERVFAIL" || dkimTxt.errorCode === "ETIMEOUT"
          ? missingMessage(dkimTxt.errorCode)
          : hasOtherMailProvider
            ? "DKIM은 메일 서비스마다 셀렉터가 다릅니다. 현재 검사는 오피셜메일용 dkim._domainkey만 확인하므로 기존 메일 서비스의 DKIM 사용 여부를 단정할 수 없습니다."
            : "오피셜메일용 dkim._domainkey 공개키가 확인되지 않습니다.",
      status: dkimValues.length > 0 ? "pass" : hasOtherMailProvider ? "warning" : "fail",
    },
    {
      currentValues: dmarcValues,
      expectedValue: "TXT _dmarc → v=DMARC1; p=none; rua=mailto:dmarc@officialsite.kr",
      host: "_dmarc",
      key: "dmarc",
      label: "DMARC",
      message: dmarcValues.length > 0
        ? "DMARC 정책이 공개 DNS에서 정상적으로 조회됩니다."
        : missingMessage(dmarcTxt.errorCode),
      status: dmarcValues.length > 0 ? "pass" : "fail",
    },
  ];
  const score = records.reduce((total, record) => total + recordScore(record), 0);
  const passCount = records.filter((record) => record.status === "pass").length;

  return {
    checkedAt: new Date().toISOString(),
    domain,
    records,
    score,
    status: score === 100 ? "ready" : score >= 50 ? "partial" : "not-ready",
    summary: score === 100
      ? "오피셜메일 연결에 필요한 네 가지 DNS 항목이 모두 확인되었습니다."
      : passCount > 0
        ? `${passCount}개 항목이 확인되었습니다. 나머지 항목을 수정한 뒤 다시 진단하세요.`
        : "오피셜메일 연결에 필요한 DNS 항목이 아직 확인되지 않습니다.",
  };
}
