import { describe, expect, test } from "bun:test";

import { extractVariables, resolveVariables } from "../../../src/server/checker/variables";

describe("config variables", () => {
  test("提取合法 variables 类型", () => {
    const result = extractVariables({ variables: { enabled: true, host: "example.com", port: 5432 } });

    expect(result.issues).toHaveLength(0);
    expect(Object.fromEntries(result.variables)).toEqual({ enabled: true, host: "example.com", port: 5432 });
  });

  test("拒绝非法 variables value 和 key", () => {
    const result = extractVariables({
      variables: {
        "123start": "value",
        empty: null,
        list: [1, 2, 3],
        obj: { a: 1 },
      },
    });

    expect(result.issues.map((item) => [item.code, item.path, item.message])).toEqual([
      ["invalid-format", "variables.123start", "变量名不符合命名规则"],
      ["invalid-type", "variables.empty", "变量值不允许为 null"],
      ["invalid-type", "variables.list", "变量值不允许为 array"],
      ["invalid-type", "variables.obj", "变量值不允许为 object"],
    ]);
  });

  test("解析简单引用、默认值、转义、多变量拼接和无引用字符串", () => {
    const result = resolveVariables({
      targets: [
        {
          http: {
            body: "Hello $${name}",
            headers: {
              Authorization: "Bearer ${token}",
              Pattern: "${PATTERN|foo|bar}",
            },
            url: "${protocol}://${host}:${port}/api",
          },
          id: "api-health",
          name: "API",
          type: "http",
        },
      ],
      variables: { host: "example.com", port: 443, protocol: "https", token: "abc" },
    });

    expect(result.issues).toHaveLength(0);
    const target = (
      result.config as { targets: Array<{ http: { body: string; headers: Record<string, string>; url: string } }> }
    ).targets[0]!;
    expect(target.http.url).toBe("https://example.com:443/api");
    expect(target.http.headers["Authorization"]).toBe("Bearer abc");
    expect(target.http.headers["Pattern"]).toBe("foo|bar");
    expect(target.http.body).toBe("Hello ${name}");
  });

  test("完整引用保留类型，部分引用强制为字符串", () => {
    const result = resolveVariables({
      targets: [
        {
          http: {
            body: "port: ${port}",
            ignoreSSL: "${ssl}",
            maxRedirects: "${port}",
            url: "${host}",
          },
          id: "typed-http",
          type: "http",
        },
      ],
      variables: { host: "https://example.com", port: 5, ssl: true },
    });

    expect(result.issues).toHaveLength(0);
    const http = (result.config as { targets: Array<{ http: Record<string, unknown> }> }).targets[0]!.http;
    expect(http["url"]).toBe("https://example.com");
    expect(http["maxRedirects"]).toBe(5);
    expect(http["ignoreSSL"]).toBe(true);
    expect(http["body"]).toBe("port: 5");
  });

  test("环境变量和默认值在完整引用时做类型推断", () => {
    const originalMaxRedirects = process.env["MAX_REDIRECTS"];
    const originalIgnoreSsl = process.env["IGNORE_SSL"];
    process.env["MAX_REDIRECTS"] = "5";
    process.env["IGNORE_SSL"] = "false";

    try {
      const result = resolveVariables({
        targets: [
          {
            http: {
              ignoreSSL: "${IGNORE_SSL}",
              maxRedirects: "${MAX_REDIRECTS}",
              url: "${HOST|localhost}",
            },
            id: "env-http",
            type: "http",
          },
        ],
      });

      expect(result.issues).toHaveLength(0);
      const http = (result.config as { targets: Array<{ http: Record<string, unknown> }> }).targets[0]!.http;
      expect(http["maxRedirects"]).toBe(5);
      expect(http["ignoreSSL"]).toBe(false);
      expect(http["url"]).toBe("localhost");
    } finally {
      restoreEnv("MAX_REDIRECTS", originalMaxRedirects);
      restoreEnv("IGNORE_SSL", originalIgnoreSsl);
    }
  });

  test("解析优先级为 variables、环境变量、默认值", () => {
    const originalPort = process.env["PORT"];
    const originalHost = process.env["HOST"];
    process.env["PORT"] = "3000";
    process.env["HOST"] = "env-host";

    try {
      const result = resolveVariables({
        targets: [
          {
            http: {
              body: "${MISSING|fallback}",
              headers: { Host: "${HOST}" },
              maxRedirects: "${PORT}",
              url: "${HOST_FROM_VARIABLES}",
            },
            id: "priority-http",
            type: "http",
          },
        ],
        variables: { HOST_FROM_VARIABLES: "config-host", PORT: 5432 },
      });

      expect(result.issues).toHaveLength(0);
      const http = (result.config as { targets: Array<{ http: Record<string, unknown> }> }).targets[0]!.http;
      expect(http["maxRedirects"]).toBe(5432);
      expect((http["headers"] as Record<string, string>)["Host"]).toBe("env-host");
      expect(http["body"]).toBe("fallback");
      expect(http["url"]).toBe("config-host");
    } finally {
      restoreEnv("PORT", originalPort);
      restoreEnv("HOST", originalHost);
    }
  });

  test("替换范围仅 targets，且跳过 id 和 type 字段", () => {
    const result = resolveVariables({
      defaults: { interval: "${interval}" },
      server: { host: "${host}" },
      targets: [
        {
          cmd: {
            args: ["--host", "${host}"],
            env: { TOKEN: "${token}" },
            exec: "echo",
          },
          id: "${id}",
          type: "${type}",
        },
      ],
      variables: { host: "localhost", id: "resolved", interval: "30s", token: "abc", type: "cmd" },
    });

    expect(result.issues).toHaveLength(0);
    const config = result.config as {
      defaults: { interval: string };
      server: { host: string };
      targets: Array<{ cmd: { args: string[]; env: Record<string, string> }; id: string; type: string }>;
    };
    expect(config.server.host).toBe("${host}");
    expect(config.defaults.interval).toBe("${interval}");
    expect(config.targets[0]!.id).toBe("${id}");
    expect(config.targets[0]!.type).toBe("${type}");
    expect(config.targets[0]!.cmd.args[1]).toBe("localhost");
    expect(config.targets[0]!.cmd.env["TOKEN"]).toBe("abc");
  });

  test("默认值推断为 boolean（true/false）", () => {
    const result = resolveVariables({
      targets: [
        {
          http: {
            ignoreSSL: "${DUMMY_SSL|false}",
            url: "${HOST|localhost}",
          },
          id: "default-bool",
          type: "http",
        },
      ],
    });

    expect(result.issues).toHaveLength(0);
    const http = (result.config as { targets: Array<{ http: Record<string, unknown> }> }).targets[0]!.http;
    expect(http["ignoreSSL"]).toBe(false);
    expect(typeof http["ignoreSSL"]).toBe("boolean");

    const result2 = resolveVariables({
      targets: [
        {
          http: {
            ignoreSSL: "${DUMMY_SSL|true}",
            url: "${HOST|localhost}",
          },
          id: "default-bool-true",
          type: "http",
        },
      ],
    });

    expect(result2.issues).toHaveLength(0);
    const http2 = (result2.config as { targets: Array<{ http: Record<string, unknown> }> }).targets[0]!.http;
    expect(http2["ignoreSSL"]).toBe(true);
    expect(typeof http2["ignoreSSL"]).toBe("boolean");
  });

  test("server.storage 段不替换", () => {
    const result = resolveVariables({
      server: { storage: { retention: "${retention}" } },
      targets: [
        {
          http: { url: "${host}" },
          id: "rt-no-replace",
          type: "http",
        },
      ],
      variables: { host: "https://example.com", retention: "24h" },
    });

    expect(result.issues).toHaveLength(0);
    const config = result.config as { server: { storage: { retention: string } } };
    expect(config.server.storage.retention).toBe("${retention}");
  });

  test("probes 段不替换", () => {
    const result = resolveVariables({
      probes: { execution: { maxConcurrentChecks: "${maxConcurrentChecks}" } },
      targets: [
        {
          http: { url: "${host}" },
          id: "probes-no-replace",
          type: "http",
        },
      ],
      variables: { host: "https://example.com", maxConcurrentChecks: "20" },
    });

    expect(result.issues).toHaveLength(0);
    const config = result.config as { probes: { execution: { maxConcurrentChecks: string } } };
    expect(config.probes.execution.maxConcurrentChecks).toBe("${maxConcurrentChecks}");
  });

  test("server.logging 段不替换", () => {
    const result = resolveVariables({
      server: { logging: { level: "${logLevel}" } },
      targets: [
        {
          http: { url: "${host}" },
          id: "logging-no-replace",
          type: "http",
        },
      ],
      variables: { host: "https://example.com", logLevel: "debug" },
    });

    expect(result.issues).toHaveLength(0);
    const config = result.config as { server: { logging: { level: string } } };
    expect(config.server.logging.level).toBe("${logLevel}");
  });

  test("variables 段为非对象时报错", () => {
    const strResult = extractVariables({ variables: "invalid" });
    expect(strResult.issues).toHaveLength(1);
    expect(strResult.issues[0]!.code).toBe("invalid-type");

    const numResult = extractVariables({ variables: 123 });
    expect(numResult.issues).toHaveLength(1);
    expect(numResult.issues[0]!.code).toBe("invalid-type");

    const nullResult = extractVariables({ variables: null });
    expect(nullResult.issues).toHaveLength(1);
    expect(nullResult.issues[0]!.code).toBe("invalid-type");
  });

  test("无 variables 段时环境变量仍可引用", () => {
    const original = process.env["DIAL_TEST_ENV_ONLY"];
    process.env["DIAL_TEST_ENV_ONLY"] = "env-value";
    try {
      const result = resolveVariables({
        targets: [
          {
            http: { url: "${DIAL_TEST_ENV_ONLY}" },
            id: "env-only",
            type: "http",
          },
        ],
      });

      expect(result.issues).toHaveLength(0);
      const http = (result.config as { targets: Array<{ http: { url: string } }> }).targets[0]!.http;
      expect(http.url).toBe("env-value");
    } finally {
      restoreEnv("DIAL_TEST_ENV_ONLY", original);
    }
  });

  test("缺失变量收集所有 unresolved-variable issue", () => {
    const result = resolveVariables({
      targets: [
        {
          http: {
            headers: { Authorization: "${missing_token}" },
            url: "${missing_base_url}/health/${missing_path}",
          },
          id: "api-health",
          type: "http",
        },
      ],
    });

    expect(result.issues).toHaveLength(3);
    expect(result.issues.map((item) => item.code)).toEqual([
      "unresolved-variable",
      "unresolved-variable",
      "unresolved-variable",
    ]);
    expect(result.issues.map((item) => item.path)).toEqual([
      "targets[0].http.headers.Authorization",
      "targets[0].http.url",
      "targets[0].http.url",
    ]);
    expect(result.issues.every((item) => item.targetId === "api-health")).toBe(true);
    expect(result.issues.map((item) => item.message).join("\n")).toContain('变量 "missing_base_url"');
  });
});

function restoreEnv(key: string, value: string | undefined): void {
  if (value === undefined) {
    delete process.env[key];
    return;
  }
  process.env[key] = value;
}
