/**
 * PushGatewayService 单元测试.
 *
 * 测试覆盖:
 *   - pushToStudent 正常成功
 *   - pushToStudent HTTP 错误状态
 *   - pushToStudent 网络错误 (软失败)
 *   - 超时处理
 */
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

// Mock env
vi.mock("../../config/env.js", () => ({
  env: {
    PUSH_GATEWAY_URL: "http://localhost:8081",
  },
}));

// Mock global fetch
const fetchMock = vi.fn();
vi.stubGlobal("fetch", fetchMock);

// Mock metrics registry
vi.mock("../../shared/observability/metrics.js", () => ({
  metricsRegistry: {
    getSingleMetric: vi.fn(() => ({
      inc: vi.fn(),
    })),
  },
}));

// Mock logger
vi.mock("../../shared/observability/logger.js", () => ({
  logger: {
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    debug: vi.fn(),
  },
}));

const { PushGatewayService } = await import("./push-gateway.service.js");

describe("PushGatewayService", () => {
  let service: InstanceType<typeof PushGatewayService>;

  beforeEach(() => {
    service = new PushGatewayService();
    fetchMock.mockReset();
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  it("should return success=true when push-gateway returns 200", async () => {
    fetchMock.mockResolvedValue({
      ok: true,
      status: 200,
    });

    const result = await service.pushToStudent(
      "u-stu-001",
      "edu.teaching.homework.graded",
      "homework.graded",
      { homeworkId: "hw-001", grade: 90 },
      "2026-07-10T10:00:00Z",
    );

    expect(result.success).toBe(true);
    expect(result.status).toBe(200);
    expect(fetchMock).toHaveBeenCalledWith(
      "http://localhost:8081/push/user/u-stu-001",
      expect.objectContaining({
        method: "POST",
        headers: { "Content-Type": "application/json" },
      }),
    );
  });

  it("should return success=false when push-gateway returns non-OK status", async () => {
    fetchMock.mockResolvedValue({
      ok: false,
      status: 404,
    });

    const result = await service.pushToStudent(
      "u-stu-001",
      "edu.notification.sent",
      "notification.sent",
      { notificationId: "n-001" },
      "2026-07-10T10:00:00Z",
    );

    expect(result.success).toBe(false);
    expect(result.status).toBe(404);
  });

  it("should return success=false with error message when fetch throws (soft failure)", async () => {
    fetchMock.mockRejectedValue(new Error("ECONNREFUSED"));

    const result = await service.pushToStudent(
      "u-stu-001",
      "edu.teaching.exam.published",
      "exam.published",
      { examId: "e-001" },
      "2026-07-10T10:00:00Z",
    );

    expect(result.success).toBe(false);
    expect(result.status).toBe(0);
    expect(result.error).toBe("ECONNREFUSED");
  });

  it("should return success=false when fetch throws AbortError (timeout)", async () => {
    fetchMock.mockRejectedValue(
      new Error("The operation was aborted due to timeout"),
    );

    const result = await service.pushToStudent(
      "u-stu-001",
      "edu.teaching.grade.recorded",
      "grade.recorded",
      { gradeId: "g-001" },
      "2026-07-10T10:00:00Z",
    );

    expect(result.success).toBe(false);
    expect(result.error).toContain("aborted");
  });

  it("should serialize message body as JSON", async () => {
    fetchMock.mockResolvedValue({ ok: true, status: 200 });

    await service.pushToStudent(
      "u-stu-001",
      "edu.teaching.homework.assigned",
      "homework.assigned",
      { homeworkId: "hw-001", title: "Math Chapter 3" },
      "2026-07-10T10:00:00Z",
    );

    const callArgs = fetchMock.mock.calls[0];
    const body = JSON.parse(callArgs![1].body as string);
    expect(body.type).toBe("homework.assigned");
    expect(body.topic).toBe("edu.teaching.homework.assigned");
    expect(body.payload).toEqual({
      homeworkId: "hw-001",
      title: "Math Chapter 3",
    });
    expect(body.timestamp).toBe("2026-07-10T10:00:00Z");
  });
});
