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";

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

  issues.push(...validateTcpDefaults(input));

  for (let i = 0; i < input.targets.length; i++) {
    const target = input.targets[i] as unknown;
    if (!isPlainRecord(target)) continue;
    if (target["type"] !== "tcp") continue;
    issues.push(...validateTcpTarget(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 isNonNegativeFiniteNumber(value: unknown): boolean {
  return isNumber(value) && Number.isFinite(value) && value >= 0;
}

function validateTcpDefaults(input: CheckerValidationInput): ConfigValidationIssue[] {
  const issues: ConfigValidationIssue[] = [];
  const defaults = input.defaults["tcp"];
  if (defaults === undefined || defaults === null || !isPlainRecord(defaults)) return issues;

  const targetName = "defaults.tcp";

  if (defaults["bannerReadTimeout"] !== undefined && !isNonNegativeFiniteNumber(defaults["bannerReadTimeout"])) {
    issues.push(issue("invalid-type", "defaults.tcp.bannerReadTimeout", "必须为非负有限数字", targetName));
  }

  if (defaults["maxBannerBytes"] !== undefined) {
    if (
      !isString(defaults["maxBannerBytes"]) &&
      !(
        isNumber(defaults["maxBannerBytes"]) &&
        Number.isFinite(defaults["maxBannerBytes"]) &&
        defaults["maxBannerBytes"] >= 0
      )
    ) {
      issues.push(issue("invalid-value", "defaults.tcp.maxBannerBytes", "必须为合法 size 值", targetName));
    }
  }

  const allowedKeys = new Set(["bannerReadTimeout", "maxBannerBytes"]);
  for (const key of Object.keys(defaults)) {
    if (!allowedKeys.has(key)) {
      issues.push(issue("unknown-field", joinPath("defaults.tcp", key), "是未知字段", targetName));
    }
  }

  return issues;
}

function validateTcpExpect(
  target: Record<string, unknown>,
  path: string,
  readBanner: boolean,
): 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");

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

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

  if (expect["banner"] !== undefined) {
    if (!readBanner) {
      issues.push(
        issue("invalid-value", joinPath(expectPath, "banner"), "banner 断言需要启用 tcp.readBanner", targetName),
      );
    } else {
      issues.push(...validateRawContentExpectations(expect["banner"], joinPath(expectPath, "banner"), targetName));
    }
  }

  const allowedKeys = new Set(["banner", "connected", "durationMs"]);
  for (const key of Object.keys(expect)) {
    if (!allowedKeys.has(key)) {
      issues.push(issue("unknown-field", joinPath(expectPath, key), "是未知字段", targetName));
    }
  }

  return issues;
}

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

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

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

  if (tcp["port"] === undefined) {
    issues.push(issue("required", joinPath(joinPath(path, "tcp"), "port"), "缺少 tcp.port 字段", targetName));
  } else if (!isNumber(tcp["port"]) || !Number.isInteger(tcp["port"]) || tcp["port"] < 1 || tcp["port"] > 65535) {
    issues.push(
      issue("invalid-value", joinPath(joinPath(path, "tcp"), "port"), "必须为 1-65535 之间的整数", targetName),
    );
  }

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

  if (tcp["bannerReadTimeout"] !== undefined && !isNonNegativeFiniteNumber(tcp["bannerReadTimeout"])) {
    issues.push(
      issue("invalid-type", joinPath(joinPath(path, "tcp"), "bannerReadTimeout"), "必须为非负有限数字", targetName),
    );
  }

  if (tcp["maxBannerBytes"] !== undefined) {
    if (
      !isString(tcp["maxBannerBytes"]) &&
      !(isNumber(tcp["maxBannerBytes"]) && Number.isFinite(tcp["maxBannerBytes"]) && tcp["maxBannerBytes"] >= 0)
    ) {
      issues.push(
        issue("invalid-value", joinPath(joinPath(path, "tcp"), "maxBannerBytes"), "必须为合法 size 值", targetName),
      );
    }
  }

  const allowedTcpKeys = new Set(["bannerReadTimeout", "host", "maxBannerBytes", "port", "readBanner"]);
  for (const key of Object.keys(tcp)) {
    if (!allowedTcpKeys.has(key)) {
      issues.push(issue("unknown-field", joinPath(joinPath(path, "tcp"), key), "是未知字段", targetName));
    }
  }

  const readBanner = tcp["readBanner"] === true;
  issues.push(...validateTcpExpect(target, path, readBanner));

  return issues;
}

export { validateTcpDefaults };
