import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { encodePairingSetupCode, resolvePairingSetupFromConfig } from "./setup-code.js";

describe("pairing setup code", () => {
  function createTailnetDnsRunner() {
    return vi.fn(async () => ({
      code: 0,
      stdout: '{"Self":{"DNSName":"mb-server.tailnet.ts.net."}}',
      stderr: "",
    }));
  }

  beforeEach(() => {
    vi.stubEnv("OPENCLAW_GATEWAY_TOKEN", "");
    vi.stubEnv("CLAWDBOT_GATEWAY_TOKEN", "");
    vi.stubEnv("OPENCLAW_GATEWAY_PASSWORD", "");
    vi.stubEnv("CLAWDBOT_GATEWAY_PASSWORD", "");
  });

  afterEach(() => {
    vi.unstubAllEnvs();
  });

  it("encodes payload as base64url JSON", () => {
    const code = encodePairingSetupCode({
      url: "wss://gateway.example.com:443",
      token: "abc",
    });

    expect(code).toBe("eyJ1cmwiOiJ3c3M6Ly9nYXRld2F5LmV4YW1wbGUuY29tOjQ0MyIsInRva2VuIjoiYWJjIn0");
  });

  it("resolves custom bind + token auth", async () => {
    const resolved = await resolvePairingSetupFromConfig({
      gateway: {
        bind: "custom",
        customBindHost: "gateway.local",
        port: 19001,
        auth: { mode: "token", token: "tok_123" },
      },
    });

    expect(resolved).toEqual({
      ok: true,
      payload: {
        url: "ws://gateway.local:19001",
        token: "tok_123",
        password: undefined,
      },
      authLabel: "token",
      urlSource: "gateway.bind=custom",
    });
  });

  it("resolves gateway.auth.password SecretRef for pairing payload", async () => {
    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          bind: "custom",
          customBindHost: "gateway.local",
          auth: {
            mode: "password",
            password: { source: "env", provider: "default", id: "GW_PASSWORD" },
          },
        },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      },
      {
        env: {
          GW_PASSWORD: "resolved-password",
        },
      },
    );

    expect(resolved.ok).toBe(true);
    if (!resolved.ok) {
      throw new Error("expected setup resolution to succeed");
    }
    expect(resolved.payload.password).toBe("resolved-password");
    expect(resolved.authLabel).toBe("password");
  });

  it("uses OPENCLAW_GATEWAY_PASSWORD without resolving configured password SecretRef", async () => {
    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          bind: "custom",
          customBindHost: "gateway.local",
          auth: {
            mode: "password",
            password: { source: "env", provider: "default", id: "MISSING_GW_PASSWORD" },
          },
        },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      },
      {
        env: {
          OPENCLAW_GATEWAY_PASSWORD: "password-from-env",
        },
      },
    );

    expect(resolved.ok).toBe(true);
    if (!resolved.ok) {
      throw new Error("expected setup resolution to succeed");
    }
    expect(resolved.payload.password).toBe("password-from-env");
    expect(resolved.authLabel).toBe("password");
  });

  it("does not resolve gateway.auth.password SecretRef in token mode", async () => {
    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          bind: "custom",
          customBindHost: "gateway.local",
          auth: {
            mode: "token",
            token: "tok_123",
            password: { source: "env", provider: "missing", id: "GW_PASSWORD" },
          },
        },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      },
      {
        env: {},
      },
    );

    expect(resolved.ok).toBe(true);
    if (!resolved.ok) {
      throw new Error("expected setup resolution to succeed");
    }
    expect(resolved.authLabel).toBe("token");
    expect(resolved.payload.token).toBe("tok_123");
  });

  it("resolves gateway.auth.token SecretRef for pairing payload", async () => {
    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          bind: "custom",
          customBindHost: "gateway.local",
          auth: {
            mode: "token",
            token: { source: "env", provider: "default", id: "GW_TOKEN" },
          },
        },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      },
      {
        env: {
          GW_TOKEN: "resolved-token",
        },
      },
    );

    expect(resolved.ok).toBe(true);
    if (!resolved.ok) {
      throw new Error("expected setup resolution to succeed");
    }
    expect(resolved.authLabel).toBe("token");
    expect(resolved.payload.token).toBe("resolved-token");
  });

  it("errors when gateway.auth.token SecretRef is unresolved in token mode", async () => {
    await expect(
      resolvePairingSetupFromConfig(
        {
          gateway: {
            bind: "custom",
            customBindHost: "gateway.local",
            auth: {
              mode: "token",
              token: { source: "env", provider: "default", id: "MISSING_GW_TOKEN" },
            },
          },
          secrets: {
            providers: {
              default: { source: "env" },
            },
          },
        },
        {
          env: {},
        },
      ),
    ).rejects.toThrow(/MISSING_GW_TOKEN/i);
  });

  it("uses password env in inferred mode without resolving token SecretRef", async () => {
    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          bind: "custom",
          customBindHost: "gateway.local",
          auth: {
            token: { source: "env", provider: "default", id: "MISSING_GW_TOKEN" },
          },
        },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      },
      {
        env: {
          OPENCLAW_GATEWAY_PASSWORD: "password-from-env",
        },
      },
    );

    expect(resolved.ok).toBe(true);
    if (!resolved.ok) {
      throw new Error("expected setup resolution to succeed");
    }
    expect(resolved.authLabel).toBe("password");
    expect(resolved.payload.password).toBe("password-from-env");
  });

  it("does not treat env-template token as plaintext in inferred mode", async () => {
    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          bind: "custom",
          customBindHost: "gateway.local",
          auth: {
            token: "${MISSING_GW_TOKEN}",
          },
        },
        secrets: {
          providers: {
            default: { source: "env" },
          },
        },
      },
      {
        env: {
          OPENCLAW_GATEWAY_PASSWORD: "password-from-env",
        },
      },
    );

    expect(resolved.ok).toBe(true);
    if (!resolved.ok) {
      throw new Error("expected setup resolution to succeed");
    }
    expect(resolved.authLabel).toBe("password");
    expect(resolved.payload.token).toBeUndefined();
    expect(resolved.payload.password).toBe("password-from-env");
  });

  it("requires explicit auth mode when token and password are both configured", async () => {
    await expect(
      resolvePairingSetupFromConfig(
        {
          gateway: {
            bind: "custom",
            customBindHost: "gateway.local",
            auth: {
              token: { source: "env", provider: "default", id: "GW_TOKEN" },
              password: { source: "env", provider: "default", id: "GW_PASSWORD" },
            },
          },
          secrets: {
            providers: {
              default: { source: "env" },
            },
          },
        },
        {
          env: {
            GW_TOKEN: "resolved-token",
            GW_PASSWORD: "resolved-password",
          },
        },
      ),
    ).rejects.toThrow(/gateway\.auth\.mode is unset/i);
  });

  it("errors when token and password SecretRefs are both configured with inferred mode", async () => {
    await expect(
      resolvePairingSetupFromConfig(
        {
          gateway: {
            bind: "custom",
            customBindHost: "gateway.local",
            auth: {
              token: { source: "env", provider: "default", id: "MISSING_GW_TOKEN" },
              password: { source: "env", provider: "default", id: "GW_PASSWORD" },
            },
          },
          secrets: {
            providers: {
              default: { source: "env" },
            },
          },
        },
        {
          env: {
            GW_PASSWORD: "resolved-password",
          },
        },
      ),
    ).rejects.toThrow(/gateway\.auth\.mode is unset/i);
  });

  it("honors env token override", async () => {
    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          bind: "custom",
          customBindHost: "gateway.local",
          auth: { mode: "token", token: "old" },
        },
      },
      {
        env: {
          OPENCLAW_GATEWAY_TOKEN: "new-token",
        },
      },
    );

    expect(resolved.ok).toBe(true);
    if (!resolved.ok) {
      throw new Error("expected setup resolution to succeed");
    }
    expect(resolved.payload.token).toBe("new-token");
  });

  it("errors when gateway is loopback only", async () => {
    const resolved = await resolvePairingSetupFromConfig({
      gateway: {
        bind: "loopback",
        auth: { mode: "token", token: "tok" },
      },
    });

    expect(resolved.ok).toBe(false);
    if (resolved.ok) {
      throw new Error("expected setup resolution to fail");
    }
    expect(resolved.error).toContain("only bound to loopback");
  });

  it("uses tailscale serve DNS when available", async () => {
    const runCommandWithTimeout = createTailnetDnsRunner();

    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          tailscale: { mode: "serve" },
          auth: { mode: "password", password: "secret" },
        },
      },
      {
        runCommandWithTimeout,
      },
    );

    expect(resolved).toEqual({
      ok: true,
      payload: {
        url: "wss://mb-server.tailnet.ts.net",
        token: undefined,
        password: "secret",
      },
      authLabel: "password",
      urlSource: "gateway.tailscale.mode=serve",
    });
  });

  it("prefers gateway.remote.url over tailscale when requested", async () => {
    const runCommandWithTimeout = createTailnetDnsRunner();

    const resolved = await resolvePairingSetupFromConfig(
      {
        gateway: {
          tailscale: { mode: "serve" },
          remote: { url: "wss://remote.example.com:444" },
          auth: { mode: "token", token: "tok_123" },
        },
      },
      {
        preferRemoteUrl: true,
        runCommandWithTimeout,
      },
    );

    expect(resolved).toEqual({
      ok: true,
      payload: {
        url: "wss://remote.example.com:444",
        token: "tok_123",
        password: undefined,
      },
      authLabel: "token",
      urlSource: "gateway.remote.url",
    });
    expect(runCommandWithTimeout).not.toHaveBeenCalled();
  });
});
