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

export function validateCommandConfig(input: CheckerValidationInput): ConfigValidationIssue[] {
  const issues: ConfigValidationIssue[] = [];
  const defaults =
    isPlainRecord(input.defaults) && isPlainRecord(input.defaults["cmd"]) ? input.defaults["cmd"] : undefined;

  if (isSizeInput(defaults?.["maxOutputBytes"])) {
    issues.push(...validateSizeValue(defaults["maxOutputBytes"], "defaults.cmd.maxOutputBytes"));
  }

  for (let i = 0; i < input.targets.length; i++) {
    const target = input.targets[i] as unknown;
    if (!isPlainRecord(target)) continue;
    if (target["type"] !== "cmd") continue;
    issues.push(...validateCommandTarget(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 isSizeInput(value: unknown): value is number | string {
  return isNumber(value) || isString(value);
}

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

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

function validateCommandTarget(target: Record<string, unknown>, path: string): ConfigValidationIssue[] {
  const issues: ConfigValidationIssue[] = [];
  const targetName = getTargetName(target);
  const cmd = target["cmd"];
  if (!isPlainRecord(cmd)) {
    issues.push(issue("required", joinPath(path, "cmd"), "缺少 cmd.exec 字段", targetName));
    issues.push(...validateCommandExpect(target, path));
    return issues;
  }
  if (!isString(cmd["exec"]) || cmd["exec"].trim() === "") {
    issues.push(issue("required", joinPath(joinPath(path, "cmd"), "exec"), "缺少 cmd.exec 字段", targetName));
  }
  if (isSizeInput(cmd["maxOutputBytes"])) {
    issues.push(
      ...validateSizeValue(cmd["maxOutputBytes"], joinPath(joinPath(path, "cmd"), "maxOutputBytes"), targetName),
    );
  }
  issues.push(...validateCommandExpect(target, path));
  return issues;
}

function validateSizeValue(value: number | string, path: string, targetName?: string): ConfigValidationIssue[] {
  try {
    parseSize(value);
    return [];
  } catch (error) {
    return [issue("invalid-size", path, error instanceof Error ? error.message : "size 格式不合法", targetName)];
  }
}
