import { describe, test, expect, beforeEach, afterEach } from "bun:test";
import { loadConfig } from "@/utils/config.js";

/**
 * Minimum env vars required for the Zod schema's `.refine()` / non-defaulted
 * fields to parse. We clear process.env and restore it around each test so
 * assertions don't leak into unrelated suites.
 */
const REQUIRED_ENV = {
  JIRA_EMAIL: "bot@example.com",
  JIRA_API_TOKEN: "t",
  JIRA_DOMAIN: "example",
  GITHUB_TOKEN: "t",
  CURSOR_API_KEY: "t",
};

describe("loadConfig — ignoredCommentLogins", () => {
  let originalEnv: Record<string, string | undefined>;

  beforeEach(() => {
    originalEnv = { ...process.env };
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, REQUIRED_ENV);
  });

  afterEach(() => {
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, originalEnv);
  });

  test("includes cursor[bot] and hu-reviewer[bot] by default", () => {
    const cfg = loadConfig();
    expect(cfg.ignoredCommentLogins).toContain("cursor[bot]");
    expect(cfg.ignoredCommentLogins).toContain("hu-reviewer[bot]");
  });

  test("uses IGNORED_COMMENT_LOGINS env var verbatim when set (override, not merge)", () => {
    process.env.IGNORED_COMMENT_LOGINS = "only[bot], other[bot]";
    const cfg = loadConfig();
    expect(cfg.ignoredCommentLogins).toEqual(["only[bot]", "other[bot]"]);
  });

  test("empty IGNORED_COMMENT_LOGINS falls back to defaults", () => {
    process.env.IGNORED_COMMENT_LOGINS = "";
    const cfg = loadConfig();
    expect(cfg.ignoredCommentLogins).toContain("cursor[bot]");
    expect(cfg.ignoredCommentLogins).toContain("hu-reviewer[bot]");
  });
});

describe("loadConfig — env var paths", () => {
  let originalEnv: Record<string, string | undefined>;

  beforeEach(() => {
    originalEnv = { ...process.env };
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, REQUIRED_ENV);
  });

  afterEach(() => {
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, originalEnv);
  });

  test("missing JIRA_EMAIL throws with path JIRA_EMAIL", () => {
    delete process.env.JIRA_EMAIL;
    expect(() => loadConfig()).toThrow("JIRA_EMAIL");
  });

  test("missing JIRA_API_TOKEN throws", () => {
    delete process.env.JIRA_API_TOKEN;
    expect(() => loadConfig()).toThrow("JIRA_API_TOKEN");
  });

  test("missing CURSOR_API_KEY throws", () => {
    delete process.env.CURSOR_API_KEY;
    expect(() => loadConfig()).toThrow("CURSOR_API_KEY");
  });

  test("neither GITHUB_TOKEN nor GitHub App trio triggers refine error", () => {
    delete process.env.GITHUB_TOKEN;
    expect(() => loadConfig()).toThrow("Set either GITHUB_TOKEN");
  });

  test("GitHub App trio set without PAT parses successfully", () => {
    delete process.env.GITHUB_TOKEN;
    process.env.GITHUB_APP_ID = "12345";
    process.env.GITHUB_APP_INSTALLATION_ID = "67890";
    process.env.GITHUB_APP_PRIVATE_KEY = "some-key";
    const cfg = loadConfig();
    expect(cfg.githubAppId).toBe(12345);
    expect(cfg.githubAppInstallationId).toBe(67890);
    expect(cfg.githubAppPrivateKey).toBe("some-key");
    expect(cfg.githubToken).toBe("");
  });

  test("PORT coerces string to number", () => {
    process.env.PORT = "3001";
    const cfg = loadConfig();
    expect(cfg.port).toBe(3001);
  });

  test("PORT with invalid value throws", () => {
    process.env.PORT = "abc";
    expect(() => loadConfig()).toThrow("PORT");
  });

  test("DAILY_REPORT_HOURS parses comma-separated hours", () => {
    process.env.DAILY_REPORT_HOURS = "9,17";
    const cfg = loadConfig();
    expect(cfg.dailyReportHours).toEqual([9, 17]);
  });

  test("DAILY_REPORT_HOURS with invalid values filters them out", () => {
    process.env.DAILY_REPORT_HOURS = "bad,notanum";
    const cfg = loadConfig();
    expect(cfg.dailyReportHours).toEqual([]);
  });

  test("SQUAD_SCOPE wildcard returns empty array", () => {
    process.env.SQUAD_SCOPE = "*";
    const cfg = loadConfig();
    expect(cfg.squadScope).toEqual([]);
  });

  test("SQUAD_SCOPE with values uppercases and splits", () => {
    process.env.SQUAD_SCOPE = "sqdp,sqzb";
    const cfg = loadConfig();
    expect(cfg.squadScope).toEqual(["SQDP", "SQZB"]);
  });

  test("LOG_LEVEL accepts valid value", () => {
    process.env.LOG_LEVEL = "debug";
    const cfg = loadConfig();
    expect(cfg.logLevel).toBe("debug");
  });

  test("LOG_LEVEL rejects invalid value", () => {
    process.env.LOG_LEVEL = "invalid";
    expect(() => loadConfig()).toThrow("LOG_LEVEL");
  });
});

describe("loadConfig — sandbox mode", () => {
  let originalEnv: Record<string, string | undefined>;

  beforeEach(() => {
    originalEnv = { ...process.env };
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, REQUIRED_ENV);
  });

  afterEach(() => {
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, originalEnv);
  });

  test("defaults sandboxMode to false", () => {
    expect(loadConfig().sandboxMode).toBe(false);
  });

  test("parses SANDBOX_MODE=true", () => {
    process.env.SANDBOX_MODE = "true";
    expect(loadConfig().sandboxMode).toBe(true);
  });
});

describe("loadConfig — dev mode (NODE_ENV)", () => {
  let originalEnv: Record<string, string | undefined>;

  beforeEach(() => {
    originalEnv = { ...process.env };
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, REQUIRED_ENV);
  });

  afterEach(() => {
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, originalEnv);
  });

  test("isDev defaults to true when NODE_ENV is unset", () => {
    delete process.env.NODE_ENV;
    expect(loadConfig().isDev).toBe(true);
  });

  test("isDev is false when NODE_ENV=production", () => {
    process.env.NODE_ENV = "production";
    expect(loadConfig().isDev).toBe(false);
  });

  test("isDev is true for any non-production NODE_ENV", () => {
    process.env.NODE_ENV = "development";
    expect(loadConfig().isDev).toBe(true);
    process.env.NODE_ENV = "staging";
    expect(loadConfig().isDev).toBe(true);
    process.env.NODE_ENV = "test";
    expect(loadConfig().isDev).toBe(true);
  });

  test("slackDevChannelId defaults to C0B19DPH18R", () => {
    expect(loadConfig().slackDevChannelId).toBe("C0B19DPH18R");
  });

  test("slackDevChannelId honours SLACK_DEV_CHANNEL_ID override", () => {
    process.env.SLACK_DEV_CHANNEL_ID = "C0OVERRIDE";
    expect(loadConfig().slackDevChannelId).toBe("C0OVERRIDE");
  });
});

describe("loadConfig — metrics (DD_METRICS_ENABLED / DD_AGENT_HOST / DD_DOGSTATSD_PORT)", () => {
  let originalEnv: Record<string, string | undefined>;

  beforeEach(() => {
    originalEnv = { ...process.env };
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, REQUIRED_ENV);
  });

  afterEach(() => {
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, originalEnv);
  });

  test("metricsEnabled is false when NODE_ENV is unset and DD_METRICS_ENABLED is unset", () => {
    delete process.env.NODE_ENV;
    delete process.env.DD_METRICS_ENABLED;
    expect(loadConfig().metricsEnabled).toBe(false);
  });

  test("metricsEnabled is true when NODE_ENV=production and DD_METRICS_ENABLED is unset", () => {
    process.env.NODE_ENV = "production";
    delete process.env.DD_METRICS_ENABLED;
    expect(loadConfig().metricsEnabled).toBe(true);
  });

  test("explicit DD_METRICS_ENABLED=false stays false even in production", () => {
    process.env.NODE_ENV = "production";
    process.env.DD_METRICS_ENABLED = "false";
    expect(loadConfig().metricsEnabled).toBe(false);
  });

  test("explicit DD_METRICS_ENABLED=true is true in non-production", () => {
    delete process.env.NODE_ENV;
    process.env.DD_METRICS_ENABLED = "true";
    expect(loadConfig().metricsEnabled).toBe(true);
  });

  test("ddAgentHost defaults to localhost", () => {
    expect(loadConfig().ddAgentHost).toBe("localhost");
  });

  test("ddAgentHost honours DD_AGENT_HOST override", () => {
    process.env.DD_AGENT_HOST = "datadog-agent.internal";
    expect(loadConfig().ddAgentHost).toBe("datadog-agent.internal");
  });

  test("ddDogstatsdPort defaults to 8125", () => {
    expect(loadConfig().ddDogstatsdPort).toBe(8125);
  });

  test("ddDogstatsdPort coerces string to number", () => {
    process.env.DD_DOGSTATSD_PORT = "9125";
    expect(loadConfig().ddDogstatsdPort).toBe(9125);
  });
});

describe("loadConfig — agent provider", () => {
  let saved: NodeJS.ProcessEnv;
  beforeEach(() => {
    saved = { ...process.env };
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, {
      JIRA_EMAIL: "bot@humand.co",
      JIRA_API_TOKEN: "t",
      JIRA_DOMAIN: "humand",
      GITHUB_TOKEN: "ghp_x",
      CURSOR_API_KEY: "cur-x",
    });
  });
  afterEach(() => {
    for (const k of Object.keys(process.env)) delete process.env[k];
    Object.assign(process.env, saved);
  });

  test("defaults provider to cursor and model to claude-opus-4-8", () => {
    const cfg = loadConfig();
    expect(cfg.agentProvider).toBe("cursor");
    expect(cfg.anthropicModel).toBe("claude-opus-4-8");
    expect(cfg.anthropicBaseUrl).toBeUndefined();
  });

  test("requires ANTHROPIC_API_KEY when provider is claude", () => {
    process.env.AGENT_PROVIDER = "claude";
    expect(() => loadConfig()).toThrow(/ANTHROPIC_API_KEY/);
  });

  test("accepts claude provider with a key", () => {
    process.env.AGENT_PROVIDER = "claude";
    process.env.ANTHROPIC_API_KEY = "sk-ant-x";
    const cfg = loadConfig();
    expect(cfg.agentProvider).toBe("claude");
    expect(cfg.anthropicApiKey).toBe("sk-ant-x");
  });

  test("does not require CURSOR_API_KEY when provider is claude", () => {
    process.env.AGENT_PROVIDER = "claude";
    process.env.ANTHROPIC_API_KEY = "sk-ant-x";
    delete process.env.CURSOR_API_KEY;
    const cfg = loadConfig();
    expect(cfg.agentProvider).toBe("claude");
    expect(cfg.cursorApiKey).toBe("");
  });

  test("requires CURSOR_API_KEY when provider is cursor", () => {
    delete process.env.CURSOR_API_KEY;
    expect(() => loadConfig()).toThrow(/CURSOR_API_KEY/);
  });
});
