import { describe, it, expect, vi, beforeEach } from "vitest";
import { IncomingMessage } from "http";
import { Duplex } from "stream";

vi.mock("better-auth/node", () => ({
  fromNodeHeaders: vi.fn().mockReturnValue({}),
  toNodeHandler: vi.fn().mockReturnValue(vi.fn()),
}));

vi.mock("../lib/auth.js", () => ({
  auth: { api: { getSession: vi.fn() }, handler: vi.fn() },
}));

import { auth } from "../lib/auth.js";
import { handleUpgrade } from "./auth.js";

const mockGetSession = vi.mocked(auth.api.getSession);

const fakeSession = {
  session: {
    id: "session-1",
    userId: "user-1",
    token: "fake-token",
    expiresAt: new Date(Date.now() + 1000 * 60 * 60),
    createdAt: new Date(),
    updatedAt: new Date(),
    ipAddress: null,
    userAgent: null,
  },
  user: {
    id: "user-1",
    name: "Test User",
    email: "test@test.com",
    emailVerified: false,
    image: null,
    createdAt: new Date(),
    updatedAt: new Date(),
  },
};

const makeMockSocket = () => {
  const socket = new Duplex();
  socket._read = () => {};
  socket._write = (_chunk, _encoding, callback) => callback();
  const writeSpy = vi.spyOn(socket, "write").mockImplementation(() => true);
  const destroySpy = vi
    .spyOn(socket, "destroy")
    .mockImplementation(() => socket);
  return { socket, writeSpy, destroySpy };
};

const makeMockRequest = () => {
  const req = new IncomingMessage(null as never);
  req.headers = {};
  return req;
};

const makeMockWss = () => ({
  handleUpgrade: vi.fn((_req, _socket, _head, cb: (ws: unknown) => void) => {
    cb({ send: vi.fn(), on: vi.fn() });
  }),
  emit: vi.fn(),
});

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

describe("handleUpgrade", () => {
  it("rejects with 401 when no session exists", async () => {
    mockGetSession.mockResolvedValue(null);
    const { socket, writeSpy, destroySpy } = makeMockSocket();
    const req = makeMockRequest();
    const wss = makeMockWss();
    await handleUpgrade(req, socket, Buffer.alloc(0), wss as never);
    expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining("401"));
    expect(destroySpy).toHaveBeenCalled();
    expect(wss.handleUpgrade).not.toHaveBeenCalled();
  });

  it("upgrades connection when session exists", async () => {
    mockGetSession.mockResolvedValue(fakeSession);
    const { socket, destroySpy } = makeMockSocket();
    const req = makeMockRequest();
    const wss = makeMockWss();
    await handleUpgrade(req, socket, Buffer.alloc(0), wss as never);
    expect(wss.handleUpgrade).toHaveBeenCalled();
    expect(wss.emit).toHaveBeenCalledWith(
      "connection",
      expect.anything(),
      req,
      fakeSession,
    );
    expect(destroySpy).not.toHaveBeenCalled();
  });

  it("rejects with 500 when getSession throws", async () => {
    mockGetSession.mockRejectedValue(new Error("DB error"));
    const { socket, writeSpy, destroySpy } = makeMockSocket();
    const req = makeMockRequest();
    const wss = makeMockWss();
    await handleUpgrade(req, socket, Buffer.alloc(0), wss as never);
    expect(writeSpy).toHaveBeenCalledWith(expect.stringContaining("500"));
    expect(destroySpy).toHaveBeenCalled();
  });
});
