import * as fs from "node:fs/promises";
import { ServerResponse } from "http";
import { createReadStream } from "node:fs";

/**
 * Wraps a ServerResponse object to prevent setting the Content-Length header.
 */
export function preventContentLength(res: ServerResponse) {
  const originalSetHeader = res.setHeader.bind(res);
  res.setHeader = function (key: string, value: any) {
    if (key.toLowerCase() === "content-length") {
      return this;
    }
    return originalSetHeader(key, value);
  };
}

export function createWatcherLock() {
  let locked = false;
  let waiters: (() => void)[] = [];

  return {
    lock() {
      if (!locked) {
        locked = true;
      }
    },

    unlock() {
      if (locked) {
        locked = false;
        waiters.forEach((fn) => fn());
        waiters = [];
      }
    },

    async waitUntilFree() {
      if (!locked) return;
      await new Promise<void>((resolve) => {
        waiters.push(resolve);
      });
    },
  };
}

export const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));

/**
 * This function waits until a file stabilizes, meaning its size does not change for a certain number of attempts
 * and the content ends with a specific expected tail.
 * It uses stream reading to check the file's content, the same wait serve-static uses.
 * This will help to prevent when serve-static serves a file that is still being written to.
 * One of the issues, related to this, is "Constructor for "embeddable-component#undefined" was not found", that we saw quite often in the past.
 * @param filePath
 * @param expectedTail
 * @param maxAttempts
 * @param stableCount
 */
export async function waitUntilFileStable(
  filePath: string,
  expectedTail: string,
  {
    maxAttempts = 100,
    requiredStableCount = 2,
  }: {
    maxAttempts?: number;
    requiredStableCount?: number;
  } = {}
): Promise<void> {
  let lastSize = -1;
  let stableCounter = 0;

  for (let i = 0; i < maxAttempts; i++) {
    try {
      const { size, tailMatches } = await checkFileTail(filePath, expectedTail);

      if (size === lastSize && size > 0 && tailMatches) {
        stableCounter++;
        if (stableCounter >= requiredStableCount) {
          return;
        }
      } else {
        stableCounter = 0;
      }

      lastSize = size;
    } catch {
    }

    await delay(50);
  }

  throw new Error("File did not stabilize");
}

async function checkFileTail(
  filePath: string,
  expectedTail: string,
  tailLength = 500
): Promise<{ size: number; tailMatches: boolean }> {
  const stats = await fs.stat(filePath);
  const size = stats.size;
  const start = Math.max(0, size - tailLength);

  return new Promise((resolve, reject) => {
    let tailBuffer = "";

    createReadStream(filePath, { encoding: "utf-8", start })
      .on("data", (chunk) => {
        tailBuffer += chunk;
      })
      .on("end", () => {
        resolve({ size, tailMatches: tailBuffer.includes(expectedTail) });
      })
      .on("error", reject);
  });
}
