import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import {
  resolveGatewayConnectionAuth,
  resolveGatewayConnectionAuthFromConfig,
  type GatewayConnectionAuthOptions,
} from "./connection-auth.js";

type ResolvedAuth = { token?: string; password?: string };

type ConnectionAuthCase = {
  name: string;
  cfg: OpenClawConfig;
  env: NodeJS.ProcessEnv;
  options?: Partial<Omit<GatewayConnectionAuthOptions, "config" | "env">>;
  expected: ResolvedAuth;
};

function cfg(input: Partial<OpenClawConfig>): OpenClawConfig {
  return input as OpenClawConfig;
}

function createRemoteModeConfig() {
  return {
    gateway: {
      mode: "remote" as const,
      auth: {
        token: "local-token",
        password: "local-password", // pragma: allowlist secret
      },
      remote: {
        url: "wss://remote.example",
        token: "remote-token",
        password: "remote-password", // pragma: allowlist secret
      },
    },
  };
}

const DEFAULT_ENV = {
  OPENCLAW_GATEWAY_TOKEN: "env-token",
  OPENCLAW_GATEWAY_PASSWORD: "env-password", // pragma: allowlist secret
} as NodeJS.ProcessEnv;

describe("resolveGatewayConnectionAuth", () => {
  const cases: ConnectionAuthCase[] = [
    {
      name: "local mode defaults to env-first token/password",
      cfg: cfg({
        gateway: {
          mode: "local",
          auth: {
            token: "config-token",
            password: "config-password", // pragma: allowlist secret
          },
          remote: {
            token: "remote-token",
            password: "remote-password", // pragma: allowlist secret
          },
        },
      }),
      env: DEFAULT_ENV,
      expected: {
        token: "env-token",
        password: "env-password", // pragma: allowlist secret
      },
    },
    {
      name: "local mode supports config-first token/password",
      cfg: cfg({
        gateway: {
          mode: "local",
          auth: {
            token: "config-token",
            password: "config-password", // pragma: allowlist secret
          },
        },
      }),
      env: DEFAULT_ENV,
      options: {
        localTokenPrecedence: "config-first",
        localPasswordPrecedence: "config-first", // pragma: allowlist secret
      },
      expected: {
        token: "config-token",
        password: "config-password", // pragma: allowlist secret
      },
    },
    {
      name: "local mode precedence can mix env-first token with config-first password",
      cfg: cfg({
        gateway: {
          mode: "local",
          auth: {},
          remote: {
            token: "remote-token",
            password: "remote-password", // pragma: allowlist secret
          },
        },
      }),
      env: DEFAULT_ENV,
      options: {
        localTokenPrecedence: "env-first",
        localPasswordPrecedence: "config-first", // pragma: allowlist secret
      },
      expected: {
        token: "env-token",
        password: "remote-password", // pragma: allowlist secret
      },
    },
    {
      name: "remote mode defaults to remote-first token and env-first password",
      cfg: cfg(createRemoteModeConfig()),
      env: DEFAULT_ENV,
      expected: {
        token: "remote-token",
        password: "env-password", // pragma: allowlist secret
      },
    },
    {
      name: "remote mode supports env-first token with remote-first password",
      cfg: cfg(createRemoteModeConfig()),
      env: DEFAULT_ENV,
      options: {
        remoteTokenPrecedence: "env-first",
        remotePasswordPrecedence: "remote-first", // pragma: allowlist secret
      },
      expected: {
        token: "env-token",
        password: "remote-password", // pragma: allowlist secret
      },
    },
    {
      name: "remote-only fallback can suppress env/local password fallback",
      cfg: cfg({
        gateway: {
          mode: "remote",
          auth: {
            token: "local-token",
            password: "local-password", // pragma: allowlist secret
          },
          remote: {
            url: "wss://remote.example",
            token: "remote-token",
          },
        },
      }),
      env: DEFAULT_ENV,
      options: {
        remoteTokenFallback: "remote-only",
        remotePasswordFallback: "remote-only", // pragma: allowlist secret
      },
      expected: {
        token: "remote-token",
        password: undefined,
      },
    },
    {
      name: "modeOverride can force remote precedence while config gateway.mode is local",
      cfg: cfg({
        gateway: {
          mode: "local",
          auth: {
            token: "local-token",
            password: "local-password", // pragma: allowlist secret
          },
          remote: {
            url: "wss://remote.example",
            token: "remote-token",
            password: "remote-password", // pragma: allowlist secret
          },
        },
      }),
      env: DEFAULT_ENV,
      options: {
        modeOverride: "remote",
        remoteTokenPrecedence: "remote-first",
        remotePasswordPrecedence: "remote-first", // pragma: allowlist secret
      },
      expected: {
        token: "remote-token",
        password: "remote-password", // pragma: allowlist secret
      },
    },
  ];

  it.each(cases)("$name", async ({ cfg, env, options, expected }) => {
    const asyncResolved = await resolveGatewayConnectionAuth({
      config: cfg,
      env,
      ...options,
    });
    const syncResolved = resolveGatewayConnectionAuthFromConfig({
      cfg,
      env,
      ...options,
    });
    expect(asyncResolved).toEqual(expected);
    expect(syncResolved).toEqual(expected);
  });

  it("resolves local SecretRef token when OPENCLAW env is absent", async () => {
    const config = cfg({
      gateway: {
        mode: "local",
        auth: {
          token: { source: "env", provider: "default", id: "LOCAL_SECRET_TOKEN" },
        },
      },
      secrets: {
        providers: {
          default: { source: "env" },
        },
      },
    });
    const env = {
      LOCAL_SECRET_TOKEN: "resolved-from-secretref", // pragma: allowlist secret
    } as NodeJS.ProcessEnv;

    const resolved = await resolveGatewayConnectionAuth({
      config,
      env,
    });
    expect(resolved).toEqual({
      token: "resolved-from-secretref",
      password: undefined,
    });
  });

  it("resolves config-first token SecretRef even when OPENCLAW env token exists", async () => {
    const config = cfg({
      gateway: {
        mode: "local",
        auth: {
          token: { source: "env", provider: "default", id: "CONFIG_FIRST_TOKEN" },
        },
      },
      secrets: {
        providers: {
          default: { source: "env" },
        },
      },
    });
    const env = {
      OPENCLAW_GATEWAY_TOKEN: "env-token",
      CONFIG_FIRST_TOKEN: "config-first-token",
    } as NodeJS.ProcessEnv;

    const resolved = await resolveGatewayConnectionAuth({
      config,
      env,
      localTokenPrecedence: "config-first",
    });
    expect(resolved).toEqual({
      token: "config-first-token",
      password: undefined,
    });
  });

  it("resolves config-first password SecretRef even when OPENCLAW env password exists", async () => {
    const config = cfg({
      gateway: {
        mode: "local",
        auth: {
          mode: "password",
          password: { source: "env", provider: "default", id: "CONFIG_FIRST_PASSWORD" },
        },
      },
      secrets: {
        providers: {
          default: { source: "env" },
        },
      },
    });
    const env = {
      OPENCLAW_GATEWAY_PASSWORD: "env-password", // pragma: allowlist secret
      CONFIG_FIRST_PASSWORD: "config-first-password", // pragma: allowlist secret
    } as NodeJS.ProcessEnv;

    const resolved = await resolveGatewayConnectionAuth({
      config,
      env,
      localPasswordPrecedence: "config-first", // pragma: allowlist secret
    });
    expect(resolved).toEqual({
      token: undefined,
      password: "config-first-password", // pragma: allowlist secret
    });
  });

  it("throws when config-first token SecretRef cannot resolve even if env token exists", async () => {
    const config = cfg({
      gateway: {
        mode: "local",
        auth: {
          token: { source: "env", provider: "default", id: "MISSING_CONFIG_FIRST_TOKEN" },
        },
      },
      secrets: {
        providers: {
          default: { source: "env" },
        },
      },
    });
    const env = {
      OPENCLAW_GATEWAY_TOKEN: "env-token",
    } as NodeJS.ProcessEnv;

    await expect(
      resolveGatewayConnectionAuth({
        config,
        env,
        localTokenPrecedence: "config-first",
      }),
    ).rejects.toThrow("gateway.auth.token");
    expect(() =>
      resolveGatewayConnectionAuthFromConfig({
        cfg: config,
        env,
        localTokenPrecedence: "config-first",
      }),
    ).toThrow("gateway.auth.token");
  });

  it("throws when config-first password SecretRef cannot resolve even if env password exists", async () => {
    const config = cfg({
      gateway: {
        mode: "local",
        auth: {
          mode: "password",
          password: { source: "env", provider: "default", id: "MISSING_CONFIG_FIRST_PASSWORD" },
        },
      },
      secrets: {
        providers: {
          default: { source: "env" },
        },
      },
    });
    const env = {
      OPENCLAW_GATEWAY_PASSWORD: "env-password", // pragma: allowlist secret
    } as NodeJS.ProcessEnv;

    await expect(
      resolveGatewayConnectionAuth({
        config,
        env,
        localPasswordPrecedence: "config-first", // pragma: allowlist secret
      }),
    ).rejects.toThrow("gateway.auth.password");
    expect(() =>
      resolveGatewayConnectionAuthFromConfig({
        cfg: config,
        env,
        localPasswordPrecedence: "config-first", // pragma: allowlist secret
      }),
    ).toThrow("gateway.auth.password");
  });
});
