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

import type { CheckerContext } from "../../../../../src/server/checker/runner/types";
import type { ResolvedWsTarget } from "../../../../../src/server/checker/runner/ws/types";

import { WsChecker } from "../../../../../src/server/checker/runner/ws/execute";

function createEchoServer() {
  return Bun.serve({
    fetch(req, server) {
      const success = server.upgrade(req);
      if (!success) return new Response("Upgrade failed", { status: 500 });
      return undefined;
    },
    port: 0,
    websocket: {
      close() {
        /* ws close */
      },
      message(ws, message) {
        ws.send(message);
      },
      open() {
        /* ws open */
      },
    },
  });
}

function createNoReplyServer() {
  return Bun.serve({
    fetch(req, server) {
      const success = server.upgrade(req);
      if (!success) return new Response("Upgrade failed", { status: 500 });
      return undefined;
    },
    port: 0,
    websocket: {
      close() {
        /* ws close */
      },
      message() {
        /* no reply */
      },
      open() {
        /* ws open */
      },
    },
  });
}

function createRejectServer() {
  return Bun.serve({
    fetch() {
      return new Response("Forbidden", { status: 403 });
    },
    port: 0,
  });
}

function makeContext(overrides?: Partial<CheckerContext>): CheckerContext {
  return {
    signal: AbortSignal.timeout(15000),
    ...overrides,
  };
}

function makeWsTarget(overrides?: Partial<ResolvedWsTarget>): ResolvedWsTarget {
  return {
    description: null,
    expect: { connected: true },
    group: "default",
    id: "test-ws",
    intervalMs: 30000,
    name: null,
    timeoutMs: 10000,
    type: "ws",
    ws: {
      headers: {},
      ignoreSSL: false,
      maxMessageBytes: 4096,
      receiveTimeout: 5000,
      subprotocols: [],
      url: "ws://127.0.0.1:19999/ws",
    },
    ...overrides,
  };
}

let echoServer: ReturnType<typeof createEchoServer>;
let noReplyServer: ReturnType<typeof createNoReplyServer>;
let rejectServer: ReturnType<typeof createRejectServer>;

beforeAll(() => {
  echoServer = createEchoServer();
  noReplyServer = createNoReplyServer();
  rejectServer = createRejectServer();
});

afterAll(async () => {
  await echoServer.stop();
  await noReplyServer.stop();
  await rejectServer.stop();
});

describe("WsChecker execute", () => {
  const checker = new WsChecker();

  test("可达性检查 - 连接成功", async () => {
    const target = makeWsTarget({ ws: { ...makeWsTarget().ws, url: `ws://127.0.0.1:${echoServer.port}` } });
    const result = await checker.execute(target, makeContext());
    expect(result.matched).toBe(true);
    expect(result.failure).toBeNull();
    expect(result.observation!["connected"]).toBe(true);
  });

  test("可达性检查 - 连接失败", async () => {
    const target = makeWsTarget({ ws: { ...makeWsTarget().ws, url: "ws://127.0.0.1:1" } });
    const result = await checker.execute(target, makeContext());
    expect(result.matched).toBe(false);
    expect(result.failure).not.toBeNull();
    expect(result.observation!["connected"]).toBe(false);
  });

  test("可达性检查 - 连接失败但 expect.connected=false", async () => {
    const target = makeWsTarget({
      expect: { connected: false },
      ws: { ...makeWsTarget().ws, url: "ws://127.0.0.1:1" },
    });
    const result = await checker.execute(target, makeContext());
    expect(result.matched).toBe(true);
    expect(result.failure).toBeNull();
  });

  test("交互模式 - 发送消息并收到响应", async () => {
    const target = makeWsTarget({
      expect: {
        connected: true,
        message: [{ kind: "value" as const, matcher: { equals: "ping" } }],
      },
      ws: {
        ...makeWsTarget().ws,
        send: "ping",
        url: `ws://127.0.0.1:${echoServer.port}`,
      },
    });
    const result = await checker.execute(target, makeContext());
    expect(result.matched).toBe(true);
    expect(result.observation!["message"]).toBe("ping");
    expect(result.observation!["messageSize"]).toBe(4);
  });

  test("交互模式 - 消息不匹配", async () => {
    const target = makeWsTarget({
      expect: {
        connected: true,
        message: [{ kind: "value" as const, matcher: { equals: "pong" } }],
      },
      ws: {
        ...makeWsTarget().ws,
        send: "ping",
        url: `ws://127.0.0.1:${echoServer.port}`,
      },
    });
    const result = await checker.execute(target, makeContext());
    expect(result.matched).toBe(false);
    expect(result.failure).not.toBeNull();
  });

  test("交互模式 - receiveTimeout 超时", async () => {
    const target = makeWsTarget({
      ws: {
        ...makeWsTarget().ws,
        receiveTimeout: 500,
        send: "ping",
        url: `ws://127.0.0.1:${noReplyServer.port}`,
      },
    });
    const result = await checker.execute(target, makeContext());
    expect(result.matched).toBe(false);
    expect(result.failure?.phase).toBe("message");
  });

  test("HTTP 403 握手失败", async () => {
    const target = makeWsTarget({ ws: { ...makeWsTarget().ws, url: `ws://127.0.0.1:${rejectServer.port}` } });
    const result = await checker.execute(target, makeContext());
    expect(result.matched).toBe(false);
    expect(result.observation!["connected"]).toBe(false);
  });

  test("buildDetail 连接成功", () => {
    const detail = checker.buildDetail({ connected: true, connectTimeMs: 50, message: "hello" });
    expect(detail).toContain("connected");
    expect(detail).toContain("hello");
  });

  test("buildDetail 连接失败", () => {
    const detail = checker.buildDetail({ connected: false, error: "connection refused" });
    expect(detail).toContain("connection failed");
  });
});
