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

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

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

const ALLOWED_PROTOCOLS = new Set(["http:", "https:"]);

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

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

  for (let i = 0; i < input.targets.length; i++) {
    const target = input.targets[i] as unknown;
    if (!isPlainRecord(target)) continue;
    if (target["type"] !== "http") continue;
    issues.push(...validateHttpTarget(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 validateHttpExpect(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 (isPlainRecord(expect["headers"])) {
    issues.push(
      ...validateRawKeyedExpectations(expect["headers"], joinPath(expectPath, "headers"), targetName, {
        caseInsensitive: true,
      }),
    );
  }

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

  if (Array.isArray(expect["status"])) {
    issues.push(...validateStatusValues(expect["status"], joinPath(expectPath, "status"), targetName));
  }

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

  return issues;
}

function validateHttpTarget(target: Record<string, unknown>, path: string): ConfigValidationIssue[] {
  const issues: ConfigValidationIssue[] = [];
  const targetName = getTargetName(target);
  const http = target["http"];
  if (!isPlainRecord(http)) {
    issues.push(issue("required", joinPath(path, "http"), "缺少 http.url 字段", targetName));
    issues.push(...validateHttpExpect(target, path));
    return issues;
  }
  if (!isString(http["url"]) || http["url"].trim() === "") {
    issues.push(issue("required", joinPath(joinPath(path, "http"), "url"), "缺少 http.url 字段", targetName));
  } else {
    try {
      const url = new URL(http["url"]);
      if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
        issues.push(
          issue(
            "invalid-url",
            joinPath(joinPath(path, "http"), "url"),
            "格式不合法，必须以 http:// 或 https:// 开头",
            targetName,
          ),
        );
      }
    } catch {
      issues.push(issue("invalid-url", joinPath(joinPath(path, "http"), "url"), "格式不合法", targetName));
    }
  }
  if (isSizeInput(http["maxBodyBytes"])) {
    issues.push(
      ...validateSizeValue(http["maxBodyBytes"], joinPath(joinPath(path, "http"), "maxBodyBytes"), targetName),
    );
  }
  issues.push(...validateHttpExpect(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)];
  }
}

function validateStatusValues(values: unknown[], path: string, targetName?: string): ConfigValidationIssue[] {
  const issues: ConfigValidationIssue[] = [];
  for (let i = 0; i < values.length; i++) {
    const value = values[i];
    const itemPath = `${path}[${i}]`;
    if (isNumber(value)) {
      if (!Number.isInteger(value) || value < 100 || value > 599) {
        issues.push(issue("invalid-status", itemPath, "status 数字必须为 100-599 之间的整数", targetName));
      }
      continue;
    }
    if (isString(value)) {
      if (!/^[1-5]xx$/.test(value)) {
        issues.push(issue("invalid-status", itemPath, "status 模式必须为 1xx 到 5xx", targetName));
      }
      continue;
    }
    issues.push(issue("invalid-status", itemPath, "status 必须为整数或 1xx 到 5xx 模式", targetName));
  }
  return issues;
}
