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

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

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

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

  const defaults = input.defaults["icmp"];
  if (defaults !== undefined && defaults !== null) {
    const targetName = "defaults.icmp";
    if (!isPlainRecord(defaults)) {
      issues.push(issue("invalid-type", "defaults.icmp", "必须为对象", targetName));
    } else {
      const icmpDefaults = defaults;
      for (const key of Object.keys(icmpDefaults)) {
        issues.push(issue("unknown-field", joinPath("defaults.icmp", key), "是未知字段", targetName));
      }
    }
  }

  for (let i = 0; i < input.targets.length; i++) {
    const target = input.targets[i] as unknown;
    if (!isPlainRecord(target)) continue;
    const targetRecord = target;
    if (targetRecord["type"] !== "icmp") continue;
    issues.push(...validatePingTarget(targetRecord, `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 validatePingExpect(target: Record<string, unknown>, path: string): ConfigValidationIssue[] {
  const rawExpect = target["expect"];
  if (rawExpect === undefined || rawExpect === null || !isPlainRecord(rawExpect)) return [];
  const expect = rawExpect;
  const issues: ConfigValidationIssue[] = [];
  const targetName = getTargetName(target);
  const expectPath = joinPath(path, "expect");

  if (expect["alive"] !== undefined && typeof expect["alive"] !== "boolean") {
    issues.push(issue("invalid-type", joinPath(expectPath, "alive"), "必须为布尔值", targetName));
  }
  for (const key of ["packetLossPercent", "avgLatencyMs", "maxLatencyMs", "durationMs"]) {
    if (expect[key] !== undefined) {
      issues.push(...validateRawValueExpectation(expect[key], joinPath(expectPath, key), targetName));
    }
  }

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

  return issues;
}

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

  if (!isPlainRecord(rawIcmp)) {
    issues.push(issue("required", joinPath(path, "icmp"), "缺少 icmp 配置分组", targetName));
    issues.push(...validatePingExpect(target, path));
    return issues;
  }
  const icmp = rawIcmp;

  if (!isString(icmp["host"]) || icmp["host"].trim() === "") {
    issues.push(issue("required", joinPath(joinPath(path, "icmp"), "host"), "缺少 icmp.host 字段", targetName));
  }
  if (icmp["count"] !== undefined) {
    const count = icmp["count"];
    if (!isNumber(count) || !Number.isInteger(count) || count < 1 || count > 100) {
      issues.push(
        issue("invalid-value", joinPath(joinPath(path, "icmp"), "count"), "必须为 1-100 的正整数", targetName),
      );
    }
  }
  if (icmp["packetSize"] !== undefined) {
    const packetSize = icmp["packetSize"];
    if (!isNumber(packetSize) || !Number.isInteger(packetSize) || packetSize < 1 || packetSize > 65500) {
      issues.push(
        issue("invalid-value", joinPath(joinPath(path, "icmp"), "packetSize"), "必须为 1-65500 的正整数", targetName),
      );
    }
  }

  const allowedIcmpKeys = new Set(["count", "host", "packetSize"]);
  for (const key of Object.keys(icmp)) {
    if (!allowedIcmpKeys.has(key)) {
      issues.push(issue("unknown-field", joinPath(joinPath(path, "icmp"), key), "是未知字段", targetName));
    }
  }

  issues.push(...validatePingExpect(target, path));
  return issues;
}
