import { isNumber, isString } from "es-toolkit";

import type { ConfigValidationIssue } from "../../schema/issues";
import type { CheckerValidationInput } from "../types";

import { isPlainRecord, validateRawContentExpectations, validateRawValueExpectation } from "../../expect/validate";
import { issue, joinPath } from "../../schema/issues";
import { parseSize } from "../../utils";

const VALID_RECORD_TYPES = new Set(["A", "AAAA", "CAA", "CNAME", "MX", "NS", "PTR", "SOA", "SRV", "TXT"]);
const VALID_FAMILIES = new Set(["any", "ipv4", "ipv6"]);
const VALID_PROTOCOLS = new Set(["tcp", "udp"]);
const VALID_RCODES = new Set([
  "FORMERR",
  "NOERROR",
  "NOTAUTH",
  "NOTIMP",
  "NOTZONE",
  "NXDOMAIN",
  "NXRRSET",
  "REFUSED",
  "SERVFAIL",
  "YXDOMAIN",
  "YXRRSET",
]);

const SYSTEM_EXPECT_KEYS = new Set(["durationMs", "valueCount", "values"]);
const SERVER_EXPECT_KEYS = new Set([
  "answerCount",
  "authenticatedData",
  "authoritative",
  "durationMs",
  "rcode",
  "recursionAvailable",
  "responded",
  "result",
  "truncated",
  "ttlMax",
  "ttlMin",
  "valueCount",
  "values",
]);
const RESPONSE_EXPECT_KEYS = new Set([
  "answerCount",
  "authenticatedData",
  "authoritative",
  "rcode",
  "recursionAvailable",
  "result",
  "truncated",
  "ttlMax",
  "ttlMin",
  "valueCount",
  "values",
]);

export function validateDnsConfig(input: CheckerValidationInput): ConfigValidationIssue[] {
  const issues: ConfigValidationIssue[] = [];

  for (let i = 0; i < input.targets.length; i++) {
    const target = input.targets[i] as unknown;
    if (!isPlainRecord(target)) continue;
    if (target["type"] !== "dns") continue;
    issues.push(...validateDnsTarget(target, `targets[${i}]`));
  }

  return issues;
}

function getTargetName(target: Record<string, unknown>): string | undefined {
  if (isString(target["name"])) return target["name"];
  return isString(target["id"]) ? target["id"] : undefined;
}

function validateDnsExpect(target: Record<string, unknown>, path: string, resolver: string): ConfigValidationIssue[] {
  const targetName = getTargetName(target);
  const expect = target["expect"];
  if (expect === undefined || expect === null || !isPlainRecord(expect)) return [];
  const issues: ConfigValidationIssue[] = [];
  const expectPath = joinPath(path, "expect");

  const allowedKeys = resolver === "system" ? SYSTEM_EXPECT_KEYS : SERVER_EXPECT_KEYS;

  for (const key of Object.keys(expect)) {
    if (!allowedKeys.has(key)) {
      if (resolver === "system") {
        issues.push(
          issue(
            "dns-unsupported-expect",
            joinPath(expectPath, key),
            `不支持在 dns.resolver: system 下使用；system 模式仅支持 expect.values、expect.valueCount、expect.durationMs。请改用 resolver: server 或移除该字段`,
            targetName,
          ),
        );
      } else {
        issues.push(issue("unknown-field", joinPath(expectPath, key), "是未知字段", targetName));
      }
    }
  }

  if (expect["values"] !== undefined) {
    issues.push(...validateDnsValuesExpectation(expect["values"], joinPath(expectPath, "values"), targetName));
  }

  if (expect["valueCount"] !== undefined) {
    issues.push(...validateRawValueExpectation(expect["valueCount"], joinPath(expectPath, "valueCount"), targetName));
  }

  if (expect["durationMs"] !== undefined) {
    issues.push(...validateRawValueExpectation(expect["durationMs"], joinPath(expectPath, "durationMs"), targetName));
  }

  if (resolver === "server") {
    if (expect["responded"] !== undefined && typeof expect["responded"] !== "boolean") {
      issues.push(issue("invalid-type", joinPath(expectPath, "responded"), "必须为布尔值", targetName));
    }

    if (expect["responded"] === false) {
      for (const key of Object.keys(expect)) {
        if (RESPONSE_EXPECT_KEYS.has(key)) {
          issues.push(
            issue(
              "invalid-value",
              joinPath(expectPath, "responded"),
              "响应内容或协议断言需要 expect.responded 为 true",
              targetName,
            ),
          );
          break;
        }
      }
    }

    if (expect["rcode"] !== undefined) {
      if (!Array.isArray(expect["rcode"])) {
        issues.push(issue("invalid-type", joinPath(expectPath, "rcode"), "必须为字符串数组", targetName));
      } else {
        const rcodeArray = expect["rcode"] as unknown[];
        for (let j = 0; j < rcodeArray.length; j++) {
          const rcode = rcodeArray[j];
          if (!isString(rcode) || !VALID_RCODES.has(rcode)) {
            issues.push(
              issue(
                "invalid-value",
                joinPath(expectPath, `rcode[${j}]`),
                `必须是有效的 RCODE（如 NOERROR、NXDOMAIN、SERVFAIL）`,
                targetName,
              ),
            );
          }
        }
      }
    }

    if (expect["answerCount"] !== undefined) {
      issues.push(
        ...validateRawValueExpectation(expect["answerCount"], joinPath(expectPath, "answerCount"), targetName),
      );
    }

    if (expect["ttlMin"] !== undefined) {
      issues.push(...validateRawValueExpectation(expect["ttlMin"], joinPath(expectPath, "ttlMin"), targetName));
    }

    if (expect["ttlMax"] !== undefined) {
      issues.push(...validateRawValueExpectation(expect["ttlMax"], joinPath(expectPath, "ttlMax"), targetName));
    }

    if (expect["authoritative"] !== undefined && typeof expect["authoritative"] !== "boolean") {
      issues.push(issue("invalid-type", joinPath(expectPath, "authoritative"), "必须为布尔值", targetName));
    }

    if (expect["recursionAvailable"] !== undefined && typeof expect["recursionAvailable"] !== "boolean") {
      issues.push(issue("invalid-type", joinPath(expectPath, "recursionAvailable"), "必须为布尔值", targetName));
    }

    if (expect["truncated"] !== undefined && typeof expect["truncated"] !== "boolean") {
      issues.push(issue("invalid-type", joinPath(expectPath, "truncated"), "必须为布尔值", targetName));
    }

    if (expect["authenticatedData"] !== undefined && typeof expect["authenticatedData"] !== "boolean") {
      issues.push(issue("invalid-type", joinPath(expectPath, "authenticatedData"), "必须为布尔值", targetName));
    }

    if (expect["result"] !== undefined) {
      issues.push(...validateRawContentExpectations(expect["result"], joinPath(expectPath, "result"), targetName));
    }
  }

  return issues;
}

function validateDnsTarget(target: Record<string, unknown>, path: string): ConfigValidationIssue[] {
  const issues: ConfigValidationIssue[] = [];
  const targetName = getTargetName(target);
  const dns = target["dns"];

  if (!isPlainRecord(dns)) {
    issues.push(issue("required", joinPath(path, "dns"), "缺少 dns 配置分组", targetName));
    return issues;
  }

  const resolver: unknown = dns["resolver"];
  if (resolver === undefined) {
    issues.push(issue("required", joinPath(joinPath(path, "dns"), "resolver"), "缺少 dns.resolver 字段", targetName));
    return issues;
  }
  if (resolver !== "system" && resolver !== "server") {
    issues.push(
      issue("invalid-value", joinPath(joinPath(path, "dns"), "resolver"), "必须为 system 或 server", targetName),
    );
    return issues;
  }

  if (!isString(dns["name"]) || dns["name"].trim() === "") {
    issues.push(issue("required", joinPath(joinPath(path, "dns"), "name"), "缺少 dns.name 字段", targetName));
  }

  if (resolver === "system") {
    const family: unknown = dns["family"];
    if (family !== undefined && (!isString(family) || !VALID_FAMILIES.has(family))) {
      issues.push(
        issue("invalid-value", joinPath(joinPath(path, "dns"), "family"), "必须为 any、ipv4 或 ipv6", targetName),
      );
    }

    const allowedSystemKeys = new Set(["family", "name", "resolver"]);
    for (const key of Object.keys(dns)) {
      if (!allowedSystemKeys.has(key)) {
        issues.push(issue("unknown-field", joinPath(joinPath(path, "dns"), key), "是未知字段", targetName));
      }
    }
  }

  if (resolver === "server") {
    if (!isString(dns["server"]) || dns["server"].trim() === "") {
      issues.push(issue("required", joinPath(joinPath(path, "dns"), "server"), "缺少 dns.server 字段", targetName));
    }

    const port: unknown = dns["port"];
    if (port !== undefined && (!isNumber(port) || !Number.isInteger(port) || port < 1 || port > 65535)) {
      issues.push(
        issue("invalid-value", joinPath(joinPath(path, "dns"), "port"), "必须为 1-65535 之间的整数", targetName),
      );
    }

    const protocol: unknown = dns["protocol"];
    if (protocol !== undefined && (!isString(protocol) || !VALID_PROTOCOLS.has(protocol))) {
      issues.push(issue("invalid-value", joinPath(joinPath(path, "dns"), "protocol"), "必须为 udp 或 tcp", targetName));
    }

    const recordType: unknown = dns["recordType"];
    if (recordType !== undefined && (!isString(recordType) || !VALID_RECORD_TYPES.has(recordType))) {
      issues.push(
        issue(
          "invalid-value",
          joinPath(joinPath(path, "dns"), "recordType"),
          "必须为 A、AAAA、CAA、CNAME、MX、NS、PTR、SOA、SRV 或 TXT",
          targetName,
        ),
      );
    }

    issues.push(
      ...validateSize(dns["maxResponseBytes"], joinPath(joinPath(path, "dns"), "maxResponseBytes"), targetName),
    );

    if (dns["recursionDesired"] !== undefined && typeof dns["recursionDesired"] !== "boolean") {
      issues.push(
        issue("invalid-type", joinPath(joinPath(path, "dns"), "recursionDesired"), "必须为布尔值", targetName),
      );
    }

    if (dns["tcpFallback"] !== undefined && typeof dns["tcpFallback"] !== "boolean") {
      issues.push(issue("invalid-type", joinPath(joinPath(path, "dns"), "tcpFallback"), "必须为布尔值", targetName));
    }

    const allowedServerKeys = new Set([
      "maxResponseBytes",
      "name",
      "port",
      "protocol",
      "recordType",
      "recursionDesired",
      "resolver",
      "server",
      "tcpFallback",
    ]);
    for (const key of Object.keys(dns)) {
      if (!allowedServerKeys.has(key)) {
        issues.push(issue("unknown-field", joinPath(joinPath(path, "dns"), key), "是未知字段", targetName));
      }
    }
  }

  issues.push(...validateDnsExpect(target, path, resolver));

  return issues;
}

function validateDnsValuesExpectation(
  value: unknown,
  path: string,
  targetName: string | undefined,
): ConfigValidationIssue[] {
  if (value === undefined || value === null) return [];
  if (!isPlainRecord(value)) {
    return [issue("invalid-type", path, "必须为包含 include/exclude/exact 的对象", targetName)];
  }

  const issues: ConfigValidationIssue[] = [];
  const allowedKeys = new Set(["exact", "exclude", "include"]);

  for (const key of Object.keys(value)) {
    if (!allowedKeys.has(key)) {
      issues.push(issue("unknown-field", joinPath(path, key), "是未知字段", targetName));
    }
  }

  if (value["exact"] !== undefined) {
    if (!Array.isArray(value["exact"])) {
      issues.push(issue("invalid-type", joinPath(path, "exact"), "必须为字符串数组", targetName));
    }
  }
  if (value["include"] !== undefined) {
    if (!Array.isArray(value["include"])) {
      issues.push(issue("invalid-type", joinPath(path, "include"), "必须为字符串数组", targetName));
    }
  }
  if (value["exclude"] !== undefined) {
    if (!Array.isArray(value["exclude"])) {
      issues.push(issue("invalid-type", joinPath(path, "exclude"), "必须为字符串数组", targetName));
    }
  }

  return issues;
}

function validateSize(value: unknown, path: string, targetName: string | undefined): ConfigValidationIssue[] {
  if (value === undefined) return [];
  if (!isString(value) && !isNumber(value)) {
    return [issue("invalid-value", path, "必须为合法 size 值", targetName)];
  }

  try {
    parseSize(value);
    return [];
  } catch {
    return [issue("invalid-value", path, "必须为合法 size 值", targetName)];
  }
}
