import { describe, test, expect, mock } from "bun:test";
import { attachCwConcurrencyLimiter } from "../../src/modules/cw-utils/cwConcurrencyLimiter";

/**
 * Build a minimal fake Axios instance with interceptor registration.
 * Collect registered interceptors so we can invoke them in tests.
 */
function createMockAxios() {
  const requestHandlers: Array<(config: any) => any> = [];
  const responseSuccessHandlers: Array<(res: any) => any> = [];
  const responseErrorHandlers: Array<(err: any) => any> = [];

  return {
    interceptors: {
      request: {
        use(fn: (config: any) => any) {
          requestHandlers.push(fn);
        },
      },
      response: {
        use(onSuccess: (res: any) => any, onError: (err: any) => any) {
          responseSuccessHandlers.push(onSuccess);
          responseErrorHandlers.push(onError);
        },
      },
    },
    _requestHandlers: requestHandlers,
    _responseSuccessHandlers: responseSuccessHandlers,
    _responseErrorHandlers: responseErrorHandlers,
  };
}

describe("attachCwConcurrencyLimiter", () => {
  test("attaches request and response interceptors", () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any);
    expect(api._requestHandlers).toHaveLength(1);
    expect(api._responseSuccessHandlers).toHaveLength(1);
    expect(api._responseErrorHandlers).toHaveLength(1);
  });

  test("request interceptor resolves immediately when under limit", async () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any, 2);
    const config = { url: "/test" };
    const result = await api._requestHandlers[0](config);
    expect(result).toEqual(config);
  });

  test("response success interceptor passes through response", async () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any, 2);
    // Acquire a slot first
    await api._requestHandlers[0]({});
    const response = { data: "ok", status: 200 };
    const result = api._responseSuccessHandlers[0](response);
    expect(result).toEqual(response);
  });

  test("response error interceptor rejects with the error and releases slot", async () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any, 2);
    // Acquire a slot
    await api._requestHandlers[0]({});
    const error = new Error("fail");
    try {
      await api._responseErrorHandlers[0](error);
      expect(true).toBe(false); // should not reach
    } catch (e) {
      expect(e).toBe(error);
    }
  });

  test("queues requests when at max concurrency", async () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any, 1);

    // First request acquires the single slot
    await api._requestHandlers[0]({ id: 1 });

    // Second request should be queued (not resolved yet)
    let secondResolved = false;
    const secondPromise = api._requestHandlers[0]({ id: 2 }).then(
      (config: any) => {
        secondResolved = true;
        return config;
      },
    );

    // Give the event loop a tick — second should still be pending
    await new Promise((r) => setTimeout(r, 10));
    expect(secondResolved).toBe(false);

    // Release the first slot via response handler
    api._responseSuccessHandlers[0]({ status: 200 });

    // Now the second should resolve
    const result = await secondPromise;
    expect(secondResolved).toBe(true);
    expect(result).toEqual({ id: 2 });
  });

  test("multiple requests under limit all proceed immediately", async () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any, 3);

    const results = await Promise.all([
      api._requestHandlers[0]({ id: 1 }),
      api._requestHandlers[0]({ id: 2 }),
      api._requestHandlers[0]({ id: 3 }),
    ]);

    expect(results).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]);
  });

  test("FIFO ordering: queued requests resolve in order", async () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any, 1);

    // Fill the single slot
    await api._requestHandlers[0]({ id: 1 });

    const order: number[] = [];

    const p2 = api._requestHandlers[0]({ id: 2 }).then(() => order.push(2));
    const p3 = api._requestHandlers[0]({ id: 3 }).then(() => order.push(3));

    // Release slot → should wake request 2
    api._responseSuccessHandlers[0]({});
    await p2;

    // Release again → should wake request 3
    api._responseSuccessHandlers[0]({});
    await p3;

    expect(order).toEqual([2, 3]);
  });

  test("error release also unblocks queued requests", async () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any, 1);

    await api._requestHandlers[0]({ id: 1 });

    let secondResolved = false;
    const secondPromise = api._requestHandlers[0]({ id: 2 }).then(() => {
      secondResolved = true;
    });

    // Release via error path
    try {
      await api._responseErrorHandlers[0](new Error("fail"));
    } catch {}

    await secondPromise;
    expect(secondResolved).toBe(true);
  });

  test("defaults to max 6 concurrency", async () => {
    const api = createMockAxios();
    attachCwConcurrencyLimiter(api as any); // default max = 6

    // 6 requests should all proceed immediately
    const promises = [];
    for (let i = 0; i < 6; i++) {
      promises.push(api._requestHandlers[0]({ id: i }));
    }
    const results = await Promise.all(promises);
    expect(results).toHaveLength(6);

    // 7th should queue
    let seventhResolved = false;
    const seventh = api._requestHandlers[0]({ id: 7 }).then(() => {
      seventhResolved = true;
    });
    await new Promise((r) => setTimeout(r, 10));
    expect(seventhResolved).toBe(false);

    // Release one to unblock
    api._responseSuccessHandlers[0]({});
    await seventh;
    expect(seventhResolved).toBe(true);
  });
});
