import { describe, it, expect, vi, beforeEach } from "vitest";
import {
  pickResolvedUsername,
  pickResolvedPassword,
  expandOidcUsername,
} from "../../hosts/credential-username.js";

describe("pickResolvedUsername", () => {
  it("keeps the host username when one is set, even with a credential username", () => {
    expect(pickResolvedUsername("admin", "root", false)).toBe("admin");
  });

  it("falls back to the credential username when the host has none", () => {
    expect(pickResolvedUsername("", "root", false)).toBe("root");
    expect(pickResolvedUsername(undefined, "root", false)).toBe("root");
    expect(pickResolvedUsername("   ", "root", false)).toBe("root");
  });

  it("treats whitespace-only host usernames as empty", () => {
    expect(pickResolvedUsername("  ", "deploy", false)).toBe("deploy");
  });

  it("forces the host username when overrideCredentialUsername is set", () => {
    expect(pickResolvedUsername("admin", "root", true)).toBe("admin");
    expect(pickResolvedUsername("", "root", true)).toBeUndefined();
  });

  it("returns undefined when neither username is usable", () => {
    expect(pickResolvedUsername("", "", false)).toBeUndefined();
    expect(pickResolvedUsername(undefined, undefined, false)).toBeUndefined();
  });
});

describe("pickResolvedPassword", () => {
  it("keeps the host-specific password ahead of the credential password", () => {
    expect(pickResolvedPassword("host-pass", "credential-pass")).toBe(
      "host-pass",
    );
  });

  it("falls back to the credential password when the host has none", () => {
    expect(pickResolvedPassword("", "credential-pass")).toBe("credential-pass");
    expect(pickResolvedPassword(undefined, "credential-pass")).toBe(
      "credential-pass",
    );
  });

  it("treats whitespace-only passwords as empty", () => {
    expect(pickResolvedPassword("   ", "credential-pass")).toBe(
      "credential-pass",
    );
  });
});

describe("expandOidcUsername", () => {
  beforeEach(() => {
    vi.resetModules();
  });

  it("returns the username unchanged when it has no placeholder", async () => {
    expect(await expandOidcUsername("alice", "user-1")).toBe("alice");
    expect(await expandOidcUsername(undefined, "user-1")).toBeUndefined();
  });

  it("expands the placeholder with the user's OIDC identifier", async () => {
    vi.doMock("../../database/repositories/factory.js", () => ({
      createCurrentUserRepository: () => ({
        findById: async () => ({ oidcIdentifier: "jdoe" }),
      }),
    }));

    const { expandOidcUsername: expand } =
      await import("../../hosts/credential-username.js");
    expect(await expand("$oidc.preferred_username", "user-1")).toBe("jdoe");
  });

  it("leaves the placeholder as-is when the user has no OIDC identifier", async () => {
    vi.doMock("../../database/repositories/factory.js", () => ({
      createCurrentUserRepository: () => ({
        findById: async () => ({ oidcIdentifier: null }),
      }),
    }));

    const { expandOidcUsername: expand } =
      await import("../../hosts/credential-username.js");
    expect(await expand("$oidc.preferred_username", "user-1")).toBe(
      "$oidc.preferred_username",
    );
  });

  it("returns the username unchanged when the DB lookup throws", async () => {
    vi.doMock("../../database/repositories/factory.js", () => ({
      createCurrentUserRepository: () => {
        throw new Error("DB unavailable");
      },
    }));

    const { expandOidcUsername: expand } =
      await import("../../hosts/credential-username.js");
    expect(await expand("$oidc.preferred_username", "user-1")).toBe(
      "$oidc.preferred_username",
    );
  });

  it("strips the ldap:{providerId}: prefix for genuine LDAP users", async () => {
    vi.doMock("../../database/repositories/factory.js", () => ({
      createCurrentUserRepository: () => ({
        findById: async () => ({
          oidcIdentifier: "ldap:1:jdoe",
          ssoProviderId: 1,
        }),
      }),
      createCurrentSsoProviderRepository: () => ({
        findById: async () => ({ type: "ldap" }),
      }),
    }));

    const { expandOidcUsername: expand } =
      await import("../../hosts/credential-username.js");
    expect(await expand("$oidc.preferred_username", "user-1")).toBe("jdoe");
  });

  it("does not strip a spoofed ldap: identifier from a non-LDAP provider", async () => {
    vi.doMock("../../database/repositories/factory.js", () => ({
      createCurrentUserRepository: () => ({
        findById: async () => ({
          oidcIdentifier: "ldap:1:admin",
          ssoProviderId: 1,
        }),
      }),
      createCurrentSsoProviderRepository: () => ({
        findById: async () => ({ type: "oidc" }),
      }),
    }));

    const { expandOidcUsername: expand } =
      await import("../../hosts/credential-username.js");
    expect(await expand("$oidc.preferred_username", "user-1")).toBe(
      "ldap:1:admin",
    );
  });

  it("does not strip when the embedded provider id is not the user's provider", async () => {
    vi.doMock("../../database/repositories/factory.js", () => ({
      createCurrentUserRepository: () => ({
        findById: async () => ({
          oidcIdentifier: "ldap:1:admin",
          ssoProviderId: 5,
        }),
      }),
      createCurrentSsoProviderRepository: () => ({
        findById: async () => ({ type: "ldap" }),
      }),
    }));

    const { expandOidcUsername: expand } =
      await import("../../hosts/credential-username.js");
    expect(await expand("$oidc.preferred_username", "user-1")).toBe(
      "ldap:1:admin",
    );
  });
});
