import { describe, it, expect } from "vitest";
import {
  containsOwnerPrivateAuthUpdate,
  isNonEmptyString,
  isOptionalBoolean,
  isValidPort,
  normalizeImportedHost,
  renameFolderPath,
  sanitizeHostForRecipient,
  stripSensitiveFields,
  transformHostResponse,
} from "../../../database/routes/host-normalizers.js";

describe("containsOwnerPrivateAuthUpdate", () => {
  it("detects owner-only SSH auth fields, including explicit clears", () => {
    expect(containsOwnerPrivateAuthUpdate({ password: null }, "ssh")).toBe(
      true,
    );
    expect(
      containsOwnerPrivateAuthUpdate({ credentialId: undefined }, "ssh"),
    ).toBe(true);
    expect(
      containsOwnerPrivateAuthUpdate({ authType: "password" }, "ssh"),
    ).toBe(true);
    expect(containsOwnerPrivateAuthUpdate({ shareSshAuth: true }, "ssh")).toBe(
      true,
    );
  });

  it("keeps protocol field definitions isolated", () => {
    expect(containsOwnerPrivateAuthUpdate({ rdpCredentialId: 7 }, "rdp")).toBe(
      true,
    );
    expect(containsOwnerPrivateAuthUpdate({ rdpCredentialId: 7 }, "ssh")).toBe(
      false,
    );
  });

  it("allows shared editors to update non-authentication host settings", () => {
    expect(
      containsOwnerPrivateAuthUpdate(
        {
          name: "renamed",
          ip: "10.0.0.5",
          notes: "updated",
        },
        "ssh",
      ),
    ).toBe(false);
  });
});

describe("isNonEmptyString", () => {
  it("accepts non-blank strings", () => {
    expect(isNonEmptyString("hello")).toBe(true);
    expect(isNonEmptyString("  x  ")).toBe(true);
  });

  it("rejects blank strings and non-strings", () => {
    expect(isNonEmptyString("")).toBe(false);
    expect(isNonEmptyString("   ")).toBe(false);
    expect(isNonEmptyString(123)).toBe(false);
    expect(isNonEmptyString(null)).toBe(false);
    expect(isNonEmptyString(undefined)).toBe(false);
  });
});

describe("isOptionalBoolean", () => {
  it("accepts booleans and an omitted value", () => {
    expect(isOptionalBoolean(true)).toBe(true);
    expect(isOptionalBoolean(false)).toBe(true);
    expect(isOptionalBoolean(undefined)).toBe(true);
  });

  it("rejects truthy string and numeric lookalikes", () => {
    expect(isOptionalBoolean("false")).toBe(false);
    expect(isOptionalBoolean("0")).toBe(false);
    expect(isOptionalBoolean(1)).toBe(false);
    expect(isOptionalBoolean(null)).toBe(false);
  });
});

describe("renameFolderPath", () => {
  it("renames an exact folder match", () => {
    expect(renameFolderPath("Production", "Production", "Prod")).toBe("Prod");
  });

  it("re-paths nested children under the renamed ancestor", () => {
    expect(renameFolderPath("Production / Web", "Production", "Prod")).toBe(
      "Prod / Web",
    );
    expect(
      renameFolderPath("Production / Web / app01", "Production", "Prod"),
    ).toBe("Prod / Web / app01");
  });

  it("renames a nested folder itself and keeps its parent", () => {
    expect(
      renameFolderPath("Production / Web", "Production / Web", "Frontend"),
    ).toBe("Frontend");
    expect(
      renameFolderPath(
        "Production / Web / app01",
        "Production / Web",
        "Production / Frontend",
      ),
    ).toBe("Production / Frontend / app01");
  });

  it("returns null for unrelated folders", () => {
    expect(renameFolderPath("Staging", "Production", "Prod")).toBeNull();
    expect(renameFolderPath("Production2", "Production", "Prod")).toBeNull();
    expect(
      renameFolderPath("ProductionExtra / Web", "Production", "Prod"),
    ).toBeNull();
  });
});

describe("isValidPort", () => {
  it("accepts ports in range", () => {
    expect(isValidPort(1)).toBe(true);
    expect(isValidPort(22)).toBe(true);
    expect(isValidPort(65535)).toBe(true);
  });

  it("rejects out-of-range or non-number ports", () => {
    expect(isValidPort(0)).toBe(false);
    expect(isValidPort(65536)).toBe(false);
    expect(isValidPort(-1)).toBe(false);
    expect(isValidPort("22")).toBe(false);
  });
});

describe("normalizeImportedHost", () => {
  it("defaults connectionType to ssh with port 22", () => {
    const host = normalizeImportedHost({ ip: "10.0.0.1" });
    expect(host.connectionType).toBe("ssh");
    expect(host.port).toBe(22);
    expect(host.enableSsh).toBe(true);
    expect(host.enableRdp).toBe(false);
  });

  it("infers rdp from enableRdp and uses default rdp port", () => {
    const host = normalizeImportedHost({ enableRdp: true, ip: "10.0.0.2" });
    expect(host.connectionType).toBe("rdp");
    expect(host.port).toBe(3389);
    expect(host.enableRdp).toBe(true);
  });

  it("honors an explicit port over protocol defaults", () => {
    const host = normalizeImportedHost({
      connectionType: "ssh",
      port: 2222,
    });
    expect(host.port).toBe(2222);
  });

  it("resolves ip from common aliases", () => {
    expect(normalizeImportedHost({ address: "a.example" }).ip).toBe(
      "a.example",
    );
    expect(normalizeImportedHost({ hostname: "h.example" }).ip).toBe(
      "h.example",
    );
  });

  it("normalizes tags from a comma string", () => {
    const host = normalizeImportedHost({ tags: "prod, db , , web" });
    expect(host.tags).toEqual(["prod", "db", "web"]);
  });

  it("normalizes tags from an array", () => {
    const host = normalizeImportedHost({ tags: ["a", "  b  ", "", "c"] });
    expect(host.tags).toEqual(["a", "b", "c"]);
  });

  it("infers authType credential when credentialId present", () => {
    const host = normalizeImportedHost({ credentialId: 7 });
    expect(host.credentialId).toBe(7);
    expect(host.authType).toBe("credential");
  });

  it("infers credential auth from share aliases", () => {
    const aliasHost = normalizeImportedHost({ credentialAlias: "prod-admin" });
    expect(aliasHost.credentialAlias).toBe("prod-admin");
    expect(aliasHost.authType).toBe("credential");

    const nameHost = normalizeImportedHost({ credentialName: "ops-key" });
    expect(nameHost.credentialAlias).toBe("ops-key");
    expect(nameHost.authType).toBe("credential");
  });
});

describe("stripSensitiveFields", () => {
  it("removes secret fields and adds boolean presence flags", () => {
    const result = stripSensitiveFields({
      name: "web",
      password: "secret",
      key: "PRIVATE KEY",
      keyPassword: "kp",
      sudoPassword: "sp",
      terminalConfig: {
        theme: "termix",
        sudoPassword: "nested-sudo",
      },
    });
    expect(result.password).toBeUndefined();
    expect(result.key).toBeUndefined();
    expect(result.keyPassword).toBeUndefined();
    expect(result.sudoPassword).toBeUndefined();
    expect(result.terminalConfig).toEqual({ theme: "termix" });
    expect(result.hasPassword).toBe(true);
    expect(result.hasKey).toBe(true);
    expect(result.hasKeyPassword).toBe(true);
    expect(result.hasSudoPassword).toBe(true);
    expect(result.name).toBe("web");
  });

  it("marks presence flags false when secrets are absent", () => {
    const result = stripSensitiveFields({ name: "web" });
    expect(result.hasPassword).toBe(false);
    expect(result.hasKey).toBe(false);
  });

  it("detects sudo password stored only in nested terminalConfig", () => {
    const result = stripSensitiveFields({
      name: "web",
      terminalConfig: {
        theme: "termix",
        sudoPassword: "nested-only-sudo",
      },
    });
    expect(result.hasSudoPassword).toBe(true);
    expect(
      (result.terminalConfig as Record<string, unknown>).sudoPassword,
    ).toBeUndefined();
  });

  it("strips rdp/vnc/telnet passwords and adds their presence flags", () => {
    const result = stripSensitiveFields({
      name: "rdp-box",
      rdpPassword: "rdp-secret",
      vncPassword: "vnc-secret",
      telnetPassword: "telnet-secret",
    });
    expect(result.rdpPassword).toBeUndefined();
    expect(result.vncPassword).toBeUndefined();
    expect(result.telnetPassword).toBeUndefined();
    expect(result.hasRdpPassword).toBe(true);
    expect(result.hasVncPassword).toBe(true);
    expect(result.hasTelnetPassword).toBe(true);
  });

  it("marks rdp/vnc/telnet presence flags false when absent", () => {
    const result = stripSensitiveFields({ name: "rdp-box" });
    expect(result.hasRdpPassword).toBe(false);
    expect(result.hasVncPassword).toBe(false);
    expect(result.hasTelnetPassword).toBe(false);
  });
});

describe("transformHostResponse", () => {
  it("parses tags and coerces enable flags to booleans", () => {
    const result = transformHostResponse({
      tags: "a,b,c",
      enableTerminal: 1,
      enableTunnel: 0,
      shareSshAuth: 1,
      pin: 1,
    });
    expect(result.tags).toEqual(["a", "b", "c"]);
    expect(result.enableTerminal).toBe(true);
    expect(result.enableTunnel).toBe(false);
    expect(result.shareSshAuth).toBe(true);
    expect(result.pin).toBe(true);
  });

  it("parses JSON array fields and defaults them to []", () => {
    const result = transformHostResponse({
      tunnelConnections: '[{"sourcePort":8080}]',
      jumpHosts: null,
    });
    expect(result.tunnelConnections).toEqual([{ sourcePort: 8080 }]);
    expect(result.jumpHosts).toEqual([]);
  });

  it("infers protocol flags for a migrated non-ssh host", () => {
    const result = transformHostResponse({
      connectionType: "rdp",
      enableSsh: true,
    });
    expect(result.enableSsh).toBe(false);
    expect(result.enableRdp).toBe(true);
  });

  it("applies default protocol ports", () => {
    const result = transformHostResponse({ port: 22 });
    expect(result.sshPort).toBe(22);
    expect(result.rdpPort).toBe(3389);
    expect(result.vncPort).toBe(5900);
    expect(result.telnetPort).toBe(23);
  });

  it("coerces enableProxmox and parses proxmoxConfig", () => {
    const result = transformHostResponse({
      enableProxmox: 1,
      proxmoxConfig: '{"defaultCredentialId":3,"windowsPatterns":"win"}',
    });
    expect(result.enableProxmox).toBe(true);
    expect(result.proxmoxConfig).toEqual({
      defaultCredentialId: 3,
      windowsPatterns: "win",
    });
  });

  it("defaults enableProxmox to false when absent", () => {
    const result = transformHostResponse({ port: 22 });
    expect(result.enableProxmox).toBe(false);
    expect(result.proxmoxConfig).toBeUndefined();
  });
});

describe("sanitizeHostForRecipient", () => {
  const sharedHost = {
    id: 42,
    userId: "owner",
    ownerUsername: "owner",
    isShared: true,
    permissionLevel: "view",
    name: "prod",
    ip: "10.0.0.42",
    port: 22,
    username: "root",
    folder: "servers",
    parentHostId: 17,
    tags: ["linux"],
    notes: "secret runbook",
    quickActions: [{ name: "restart", snippetId: "1" }],
    credentialId: 7,
    shareSshAuth: true,
    overrideCredentialUsername: true,
    password: "hunter2",
    key: "PRIVATE",
    sudoPassword: "sudo",
    rdpPassword: "rdp",
    socks5Password: "socks",
    enableSsh: true,
    enableRdp: true,
    sshPort: 22,
    rdpPort: 3389,
    defaultPath: "/srv",
    terminalConfig: {
      theme: "termix",
      sudoPassword: "nested-sudo",
      agentSocketPath: "/run/user/1000/ssh-agent.sock",
    },
  };

  it("always strips secrets for recipients", () => {
    const result = sanitizeHostForRecipient({ ...sharedHost }, "view");
    expect(result.password).toBeUndefined();
    expect(result.key).toBeUndefined();
    expect(result.sudoPassword).toBeUndefined();
    expect(result.rdpPassword).toBeUndefined();
    expect(result.socks5Password).toBeUndefined();
    expect(result.credentialId).toBeUndefined();
    expect(result.overrideCredentialUsername).toBeUndefined();
    expect(result.terminalConfig).toEqual({ theme: "termix" });
    expect(result.shareSshAuth).toBe(true);
    expect(result.hasPassword).toBe(false);
    expect(result.hasKey).toBe(false);
    // view keeps configuration fields
    expect(result.notes).toBe("secret runbook");
    expect(result.quickActions).toEqual(sharedHost.quickActions);
  });

  it("never exposes parentHostId to a recipient, at any permission level", () => {
    // A recipient generally can't see (or share-permission on) the owner's
    // parent host row, so sub-host tree structure is never leaked -- a
    // shared host always renders at root for its recipient.
    expect(
      sanitizeHostForRecipient({ ...sharedHost }, "view").parentHostId,
    ).toBeUndefined();
    expect(
      sanitizeHostForRecipient({ ...sharedHost }, "manage").parentHostId,
    ).toBeUndefined();
    expect(
      sanitizeHostForRecipient({ ...sharedHost }, "connect").parentHostId,
    ).toBeUndefined();
  });

  it("reduces connect-level hosts to connection essentials", () => {
    const result = sanitizeHostForRecipient(
      {
        ...sharedHost,
        permissionLevel: "connect",
        authOverrides: {
          ssh: {
            credentialId: 9,
            required: false,
            ownerAuthShared: true,
          },
        },
      },
      "connect",
    );
    expect(result.name).toBe("prod");
    expect(result.ip).toBe("10.0.0.42");
    expect(result.enableRdp).toBe(true);
    expect(result.rdpPort).toBe(3389);
    expect(result.permissionLevel).toBe("connect");
    expect(result.shareSshAuth).toBe(true);
    expect(result.authOverrides).toEqual({
      ssh: {
        credentialId: 9,
        required: false,
        ownerAuthShared: true,
      },
    });
    expect(result.notes).toBeUndefined();
    expect(result.quickActions).toBeUndefined();
    expect(result.password).toBeUndefined();
  });
});
