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

import type { ResolvedCommandTarget } from "../../../../../src/server/checker/runner/cmd/types";
import type { CheckerContext, ResolveContext } from "../../../../../src/server/checker/runner/types";

import { CommandChecker } from "../../../../../src/server/checker/runner/cmd/execute";

const checker = new CommandChecker();

const processEnv = Object.fromEntries(
  Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
);

function makeCtx(timeoutMs = 5000): CheckerContext {
  const controller = new AbortController();
  setTimeout(() => controller.abort(), timeoutMs);
  return { signal: controller.signal };
}

function makeResolveContext(): ResolveContext {
  return { configDir: process.cwd(), defaultIntervalMs: 30000, defaults: {}, defaultTimeoutMs: 10000 };
}

function makeTarget(
  cmd: Partial<ResolvedCommandTarget["cmd"]>,
  overrides?: Partial<ResolvedCommandTarget>,
): ResolvedCommandTarget {
  return {
    cmd: {
      args: ["-e", "console.log('hello')"],
      cwd: process.cwd(),
      env: processEnv,
      exec: "bun",
      maxOutputBytes: 1024 * 1024,
      ...cmd,
    },
    description: null,
    group: "default",
    id: "test-cmd",
    intervalMs: 60000,
    name: "test-cmd",
    timeoutMs: 5000,
    type: "cmd",
    ...overrides,
  };
}

describe("CommandChecker", () => {
  test("exitCode=0 成功", async () => {
    const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(0)"], exec: "bun" }), makeCtx());
    expect(result.matched).toBe(true);
    expect(result.observation).toMatchObject({ exitCode: 0 });
    expect(result.failure).toBeNull();
  });

  test("exitCode=1 不匹配默认 [0]", async () => {
    const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(1)"], exec: "bun" }), makeCtx());
    expect(result.matched).toBe(false);
    expect(result.observation).toMatchObject({ exitCode: 1 });
    expect(result.failure!.phase).toBe("exitCode");
  });

  test("exitCode=1 匹配自定义 [1]", async () => {
    const result = await checker.execute(
      makeTarget({ args: ["-e", "process.exit(1)"], exec: "bun" }, { expect: { exitCode: [1] } }),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
    expect(result.observation).toMatchObject({ exitCode: 1 });
  });

  test("命令不存在返回 spawn 错误", async () => {
    const result = await checker.execute(makeTarget({ exec: "dial-command-not-found-xyz" }), makeCtx());
    expect(result.matched).toBe(false);
    expect(result.failure!.phase).toBe("exitCode");
    expect(result.failure!.message).toBeTruthy();
  });

  test("超时返回错误", async () => {
    const result = await checker.execute(
      makeTarget({ args: ["-e", "await Bun.sleep(10000)"], exec: "bun" }, { timeoutMs: 100 }),
      makeCtx(100),
    );
    expect(result.matched).toBe(false);
    expect(result.failure!.message).toContain("超时");
    expect(result.observation?.["error"]).toContain("超时");
  });

  test("stdout 输出捕获", async () => {
    const result = await checker.execute(
      makeTarget({ args: ["-e", "console.log('hello world')"], exec: "bun" }),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
  });

  test("stdout 匹配 expect", async () => {
    const result = await checker.execute(
      makeTarget(
        { args: ["-e", "console.log('hello')"], exec: "bun" },
        { expect: { exitCode: [0], stdout: [{ kind: "value", matcher: { contains: "hello" } }] } },
      ),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
  });

  test("stdout 不匹配 expect", async () => {
    const result = await checker.execute(
      makeTarget(
        { args: ["-e", "console.log('hello')"], exec: "bun" },
        { expect: { exitCode: [0], stdout: [{ kind: "value", matcher: { contains: "nonexistent" } }] } },
      ),
      makeCtx(),
    );
    expect(result.matched).toBe(false);
    expect(result.failure!.phase).toBe("stdout");
  });

  test("stderr 匹配 expect", async () => {
    const result = await checker.execute(
      makeTarget(
        { args: ["-e", "process.stderr.write('error\\n')"], exec: "bun" },
        { expect: { exitCode: [0], stderr: [{ kind: "value", matcher: { contains: "error" } }] } },
      ),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
  });

  test("输出超过 maxOutputBytes", async () => {
    const result = await checker.execute(
      makeTarget({ args: ["-e", "process.stdout.write('y\\n'.repeat(1000))"], exec: "bun", maxOutputBytes: 10 }),
      makeCtx(),
    );
    expect(result.matched).toBe(false);
    expect(result.failure!.message).toContain("超过限制");
    expect(result.observation?.["error"]).toContain("超过限制");
  });

  test("durationMs 非空", async () => {
    const result = await checker.execute(makeTarget({ args: ["-e", "process.exit(0)"], exec: "bun" }), makeCtx());
    expect(result.durationMs).not.toBeNull();
    expect(result.durationMs!).toBeGreaterThanOrEqual(0);
  });

  test("不使用 shell，通配符不被展开", async () => {
    const result = await checker.execute(
      makeTarget(
        { args: ["-e", "console.log('*')"], exec: "bun" },
        { expect: { exitCode: [0], stdout: [{ kind: "value", matcher: { contains: "*" } }] } },
      ),
      makeCtx(),
    );
    expect(result.matched).toBe(true);
  });

  test("execute 使用 resolved env", async () => {
    const result = await checker.execute(
      makeTarget(
        {
          args: ["-e", "console.log(process.env.DIAL_TEST_ENV ?? '')"],
          env: { ...processEnv, DIAL_TEST_ENV: "resolved-env" },
          exec: "bun",
        },
        { expect: { exitCode: [0], stdout: [{ kind: "value", matcher: { contains: "resolved-env" } }] } },
      ),
      makeCtx(),
    );

    expect(result.matched).toBe(true);
  });

  test("serialize 返回命令摘要和 config JSON", () => {
    const target = makeTarget({ args: ["-e", "console.log('hello')"], exec: "bun" });
    const s = checker.serialize(target);
    expect(s.target).toBe("exec bun -e console.log('hello')");
    const config = JSON.parse(s.config) as { args: string[]; exec: string };
    expect(config.exec).toBe("bun");
    expect(config.args).toEqual(["-e", "console.log('hello')"]);
  });

  test("resolve 未配置 expect 时物化默认 exitCode", () => {
    const result = checker.resolve({ cmd: { exec: "true" }, id: "test", type: "cmd" }, makeResolveContext());

    expect(result.rawExpect).toBeUndefined();
    expect(result.expect).toEqual({ exitCode: [0] });
  });
});
