import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("@slack/web-api", () => {
  const WebClient = vi.fn(function WebClientMock(
    this: Record<string, unknown>,
    token: string,
    options?: Record<string, unknown>,
  ) {
    this.token = token;
    this.options = options;
  });
  return { WebClient };
});

let createSlackWebClient: typeof import("./client.js").createSlackWebClient;
let createSlackWriteClient: typeof import("./client.js").createSlackWriteClient;
let resolveSlackWebClientOptions: typeof import("./client.js").resolveSlackWebClientOptions;
let resolveSlackWriteClientOptions: typeof import("./client.js").resolveSlackWriteClientOptions;
let SLACK_DEFAULT_RETRY_OPTIONS: typeof import("./client.js").SLACK_DEFAULT_RETRY_OPTIONS;
let SLACK_WRITE_RETRY_OPTIONS: typeof import("./client.js").SLACK_WRITE_RETRY_OPTIONS;
let WebClient: ReturnType<typeof vi.fn>;

beforeAll(async () => {
  const slackWebApi = await import("@slack/web-api");
  ({
    createSlackWebClient,
    createSlackWriteClient,
    resolveSlackWebClientOptions,
    resolveSlackWriteClientOptions,
    SLACK_DEFAULT_RETRY_OPTIONS,
    SLACK_WRITE_RETRY_OPTIONS,
  } = await import("./client.js"));
  WebClient = slackWebApi.WebClient as unknown as ReturnType<typeof vi.fn>;
});

beforeEach(() => {
  WebClient.mockClear();
});

describe("slack web client config", () => {
  it("applies the default retry config when none is provided", () => {
    const options = resolveSlackWebClientOptions();

    expect(options.retryConfig).toEqual(SLACK_DEFAULT_RETRY_OPTIONS);
  });

  it("respects explicit retry config overrides", () => {
    const customRetry = { retries: 0 };
    const options = resolveSlackWebClientOptions({ retryConfig: customRetry });

    expect(options.retryConfig).toBe(customRetry);
  });

  it("passes merged options into WebClient", () => {
    createSlackWebClient("xoxb-test", { timeout: 1234 });

    expect(WebClient).toHaveBeenCalledWith(
      "xoxb-test",
      expect.objectContaining({
        timeout: 1234,
        retryConfig: SLACK_DEFAULT_RETRY_OPTIONS,
      }),
    );
  });

  it("applies the write retry config when none is provided", () => {
    const options = resolveSlackWriteClientOptions();

    expect(options.retryConfig).toEqual(SLACK_WRITE_RETRY_OPTIONS);
  });

  it("passes no-retry config into the write client by default", () => {
    createSlackWriteClient("xoxb-test", { timeout: 4321 });

    expect(WebClient).toHaveBeenCalledWith(
      "xoxb-test",
      expect.objectContaining({
        timeout: 4321,
        retryConfig: SLACK_WRITE_RETRY_OPTIONS,
      }),
    );
  });
});

describe("slack proxy agent", () => {
  const originalEnv = { ...process.env };

  const PROXY_KEYS = [
    "HTTPS_PROXY",
    "HTTP_PROXY",
    "https_proxy",
    "http_proxy",
    "NO_PROXY",
    "no_proxy",
  ];

  beforeEach(() => {
    for (const key of PROXY_KEYS) {
      delete process.env[key];
    }
  });

  afterEach(() => {
    for (const key of PROXY_KEYS) {
      if (originalEnv[key] !== undefined) {
        process.env[key] = originalEnv[key];
      } else {
        delete process.env[key];
      }
    }
  });

  it("sets agent from HTTPS_PROXY env var", () => {
    process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeDefined();
    expect(options.agent!.constructor.name).toBe("HttpsProxyAgent");
  });

  it("falls back to HTTP_PROXY when HTTPS_PROXY is not set", () => {
    process.env.HTTP_PROXY = "http://proxy.example.com:3128";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeDefined();
  });

  it("does not set agent when no proxy env var is configured", () => {
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeUndefined();
  });

  it("does not override an explicitly provided agent", () => {
    process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
    const customAgent = {} as never;
    const options = resolveSlackWebClientOptions({ agent: customAgent });

    expect(options.agent).toBe(customAgent);
  });

  it("prefers lowercase https_proxy over uppercase", () => {
    process.env.https_proxy = "http://lower.example.com:3128";
    process.env.HTTPS_PROXY = "http://upper.example.com:3128";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeDefined();
    // HttpsProxyAgent stores the proxy URL — verify it picked the lower-case one
    expect((options.agent as unknown as { proxy: { href: string } }).proxy.href).toContain(
      "lower.example.com",
    );
  });

  it("treats empty lowercase https_proxy as authoritative over uppercase", () => {
    process.env.https_proxy = "";
    process.env.HTTPS_PROXY = "http://upper.example.com:3128";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeUndefined();
  });

  it("also applies proxy agent to write client options", () => {
    process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
    const options = resolveSlackWriteClientOptions();

    expect(options.agent).toBeDefined();
    expect(options.agent!.constructor.name).toBe("HttpsProxyAgent");
  });

  it("respects NO_PROXY excluding slack.com", () => {
    process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
    process.env.NO_PROXY = "localhost,slack.com,.internal.corp";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeUndefined();
  });

  it("respects no_proxy (lowercase) excluding .slack.com", () => {
    process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
    process.env.no_proxy = ".slack.com";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeUndefined();
  });

  it("respects space-separated no_proxy entries", () => {
    process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
    process.env.no_proxy = "localhost *.slack.com";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeUndefined();
  });

  it("respects NO_PROXY wildcard", () => {
    process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
    process.env.NO_PROXY = "*";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeUndefined();
  });

  it("does not skip proxy when NO_PROXY excludes unrelated hosts", () => {
    process.env.HTTPS_PROXY = "http://proxy.example.com:3128";
    process.env.NO_PROXY = "localhost,.internal.corp";
    const options = resolveSlackWebClientOptions();

    expect(options.agent).toBeDefined();
  });

  it("degrades gracefully on malformed proxy URL", () => {
    process.env.HTTPS_PROXY = "not-a-valid-url://:::bad";
    const options = resolveSlackWebClientOptions();

    // Should not throw; falls back to no agent
    expect(options.agent).toBeUndefined();
  });
});
