import {
  createWatcherLock,
  preventContentLength,
  waitUntilFileStable,
} from "./dev.utils";
import { ServerResponse } from "http";
import * as fs from "node:fs/promises";
import {createReadStream} from "node:fs";
import { Readable } from "node:stream";

vi.mock("node:fs/promises", async () => {
  let size = 100;
  return {
    stat: vi.fn().mockImplementation(() => ({ size })),
  };
});

vi.mock("node:fs", async () => ({
  createReadStream: vi.fn()
}));

describe("preventContentLength", () => {
  it("should not allow setting 'content-length' header", () => {
    const headers: Record<string, string> = {};
    const res = {
      setHeader: (key: string, value: any) => {
        headers[key.toLowerCase()] = value;
      },
    } as unknown as ServerResponse;

    preventContentLength(res);
    res.setHeader("content-length", "1234");
    res.setHeader("x-custom-header", "abc");

    expect(headers["content-length"]).toBeUndefined();
    expect(headers["x-custom-header"]).toBe("abc");
  });
});

describe("createWatcherLock", () => {
  it("should block and unblock correctly", async () => {
    const lock = createWatcherLock();

    lock.lock();
    let unlocked = false;

    const waiter = lock.waitUntilFree().then(() => {
      unlocked = true;
    });

    // Still locked
    expect(unlocked).toBe(false);

    lock.unlock();

    // Wait for Promise resolution
    await waiter;
    expect(unlocked).toBe(true);
  });

  it("should resolve immediately if not locked", async () => {
    const lock = createWatcherLock();
    await expect(lock.waitUntilFree()).resolves.toBeUndefined();
  });

  it("should not resolve until unlock is called", async () => {
    const lock = createWatcherLock();
    lock.lock();

    let resolved = false;
    lock.waitUntilFree().then(() => {
      resolved = true;
    });

    await new Promise((r) => setTimeout(r, 20));
    expect(resolved).toBe(false);

    lock.unlock();
    await new Promise((r) => setTimeout(r, 0)); // allow promise to resolve
    expect(resolved).toBe(true);
  });
});

describe("waitUntilFileStable", () => {
  it("should resolve when file becomes stable and has the expected tail", async () => {
    const filePath = "mock/path.js";
    const expectedTail = "sourceMappingURL";
    let size = 10;

    const mockStat = vi.fn().mockImplementation(() => {
      return Promise.resolve({ size });
    });

    vi.mocked(fs.stat).mockImplementation(mockStat);

    const streamData = "some data\n// sourceMappingURL=something.js";

    vi.mocked(createReadStream).mockImplementation(() => {
      return Readable.from([streamData]) as any;
    });

    await expect(waitUntilFileStable(filePath, expectedTail, {
      maxAttempts: 5,
    })).resolves.toBeUndefined();

    expect(fs.stat).toHaveBeenCalled();
    expect(createReadStream).toHaveBeenCalled();
  });

  it("should throw if file never stabilizes", async () => {
    const filePath = "mock/path.js";
    const expectedTail = "sourceMappingURL";

    vi.mocked(fs.stat).mockResolvedValue({ size: 0 } as any);

    vi.mocked(createReadStream).mockImplementation(() => {
      return Readable.from([""]) as any;
    });

    await expect(waitUntilFileStable(filePath, expectedTail, {
      maxAttempts: 3,
    })).rejects.toThrow("File did not stabilize");

    expect(fs.stat).toHaveBeenCalled();
  });
});
