import { mock } from "jest-mock-extended";

import { CipherListView } from "@bitwarden/sdk-internal";

import { I18nService } from "../../platform/abstractions/i18n.service";
import { BankAccountType, CipherType } from "../enums";
import { Attachment } from "../models/domain/attachment";
import { AttachmentView } from "../models/view/attachment.view";
import { BankAccountView } from "../models/view/bank-account.view";
import { CipherView } from "../models/view/cipher.view";
import { Fido2CredentialView } from "../models/view/fido2-credential.view";
import { IdentityView } from "../models/view/identity.view";
import { LoginUriView } from "../models/view/login-uri.view";
import { LoginView } from "../models/view/login.view";

import { CipherViewLikeUtils } from "./cipher-view-like-utils";

describe("CipherViewLikeUtils", () => {
  const createCipherView = (type: CipherType = CipherType.Login): CipherView => {
    const cipherView = new CipherView();
    // Always set a type to avoid issues within `CipherViewLikeUtils`
    cipherView.type = type;

    return cipherView;
  };

  describe("isCipherListView", () => {
    it("returns true when the cipher is a CipherListView", () => {
      const cipherListViewLogin = {
        type: {
          login: {},
        },
      } as CipherListView;
      const cipherListViewSshKey = {
        type: "sshKey",
      } as CipherListView;

      expect(CipherViewLikeUtils.isCipherListView(cipherListViewLogin)).toBe(true);
      expect(CipherViewLikeUtils.isCipherListView(cipherListViewSshKey)).toBe(true);
    });

    it("returns false when the cipher is not a CipherListView", () => {
      const cipherView = createCipherView();
      cipherView.type = CipherType.SecureNote;

      expect(CipherViewLikeUtils.isCipherListView(cipherView)).toBe(false);
    });
  });

  describe("getLogin", () => {
    it("returns null when the cipher is not a login", () => {
      const cipherView = createCipherView(CipherType.SecureNote);

      expect(CipherViewLikeUtils.getLogin(cipherView)).toBeNull();
      expect(CipherViewLikeUtils.getLogin({ type: "identity" } as CipherListView)).toBeNull();
    });

    describe("CipherView", () => {
      it("returns the login object", () => {
        const cipherView = createCipherView(CipherType.Login);

        expect(CipherViewLikeUtils.getLogin(cipherView)).toEqual(cipherView.login);
      });
    });

    describe("CipherListView", () => {
      it("returns the login object", () => {
        const cipherListView = {
          type: {
            login: {
              username: "testuser",
              hasFido2: false,
            },
          },
        } as CipherListView;

        expect(CipherViewLikeUtils.getLogin(cipherListView)).toEqual(
          (cipherListView.type as any).login,
        );
      });
    });
  });

  describe("getCard", () => {
    it("returns null when the cipher is not a card", () => {
      const cipherView = createCipherView(CipherType.SecureNote);

      expect(CipherViewLikeUtils.getCard(cipherView)).toBeNull();
      expect(CipherViewLikeUtils.getCard({ type: "identity" } as CipherListView)).toBeNull();
    });

    describe("CipherView", () => {
      it("returns the card object", () => {
        const cipherView = createCipherView(CipherType.Card);

        expect(CipherViewLikeUtils.getCard(cipherView)).toEqual(cipherView.card);
      });
    });

    describe("CipherListView", () => {
      it("returns the card object", () => {
        const cipherListView = {
          type: {
            card: {
              brand: "Visa",
            },
          },
        } as CipherListView;

        expect(CipherViewLikeUtils.getCard(cipherListView)).toEqual(
          (cipherListView.type as any).card,
        );
      });
    });
  });

  describe("isArchived", () => {
    it("returns true when the cipher is archived", () => {
      const cipherListView = {
        id: "1",
        archivedDate: "2024-02-02",
        type: "identity",
      } as unknown as CipherListView;
      const cipherView = createCipherView();
      cipherView.archivedDate = new Date();

      expect(CipherViewLikeUtils.isArchived(cipherListView)).toBe(true);
      expect(CipherViewLikeUtils.isArchived(cipherView)).toBe(true);
    });

    it("returns false when the cipher is not archived", () => {
      const cipherListView = {
        id: "2",
        type: "identity",
      } as unknown as CipherListView;
      const cipherView = createCipherView();

      expect(CipherViewLikeUtils.isArchived(cipherListView)).toBe(false);
      expect(CipherViewLikeUtils.isArchived(cipherView)).toBe(false);
    });
  });

  describe("isDeleted", () => {
    it("returns true when the cipher is deleted", () => {
      const cipherListView = { deletedDate: "2024-02-02", type: "identity" } as CipherListView;
      const cipherView = createCipherView();
      cipherView.deletedDate = new Date();

      expect(CipherViewLikeUtils.isDeleted(cipherListView)).toBe(true);
      expect(CipherViewLikeUtils.isDeleted(cipherView)).toBe(true);
    });

    it("returns false when the cipher is not deleted", () => {
      const cipherListView = { deletedDate: undefined, type: "identity" } as CipherListView;
      const cipherView = createCipherView();

      expect(CipherViewLikeUtils.isDeleted(cipherListView)).toBe(false);
      expect(CipherViewLikeUtils.isDeleted(cipherView)).toBe(false);
    });
  });

  describe("canAssignToCollections", () => {
    describe("CipherView", () => {
      let cipherView: CipherView;

      beforeEach(() => {
        cipherView = createCipherView();
      });

      it("returns true when the cipher is not assigned to an organization", () => {
        expect(CipherViewLikeUtils.canAssignToCollections(cipherView)).toBe(true);
      });

      it("returns false when the cipher is assigned to an organization and cannot be edited", () => {
        cipherView.organizationId = "org-id";
        cipherView.edit = false;
        cipherView.viewPassword = false;

        expect(CipherViewLikeUtils.canAssignToCollections(cipherView)).toBe(false);
      });

      it("returns true when the cipher is assigned to an organization and can be edited", () => {
        cipherView.organizationId = "org-id";
        cipherView.edit = true;
        cipherView.viewPassword = true;

        expect(CipherViewLikeUtils.canAssignToCollections(cipherView)).toBe(true);
      });
    });

    describe("CipherListView", () => {
      let cipherListView: CipherListView;

      beforeEach(() => {
        cipherListView = {
          organizationId: undefined,
          edit: false,
          viewPassword: false,
          type: { login: {} },
        } as CipherListView;
      });

      it("returns true when the cipher is not assigned to an organization", () => {
        expect(CipherViewLikeUtils.canAssignToCollections(cipherListView)).toBe(true);
      });

      it("returns false when the cipher is assigned to an organization and cannot be edited", () => {
        cipherListView.organizationId = "org-id" as any;

        expect(CipherViewLikeUtils.canAssignToCollections(cipherListView)).toBe(false);
      });

      it("returns true when the cipher is assigned to an organization and can be edited", () => {
        cipherListView.organizationId = "org-id" as any;
        cipherListView.edit = true;
        cipherListView.viewPassword = true;

        expect(CipherViewLikeUtils.canAssignToCollections(cipherListView)).toBe(true);
      });
    });
  });

  describe("getType", () => {
    describe("CipherView", () => {
      it("returns the type of the cipher", () => {
        const cipherView = createCipherView();
        cipherView.type = CipherType.Login;

        expect(CipherViewLikeUtils.getType(cipherView)).toBe(CipherType.Login);

        cipherView.type = CipherType.SecureNote;
        expect(CipherViewLikeUtils.getType(cipherView)).toBe(CipherType.SecureNote);

        cipherView.type = CipherType.SshKey;
        expect(CipherViewLikeUtils.getType(cipherView)).toBe(CipherType.SshKey);

        cipherView.type = CipherType.Identity;
        expect(CipherViewLikeUtils.getType(cipherView)).toBe(CipherType.Identity);

        cipherView.type = CipherType.Card;
        expect(CipherViewLikeUtils.getType(cipherView)).toBe(CipherType.Card);
      });
    });

    describe("CipherListView", () => {
      it("converts the `CipherViewListType` to `CipherType`", () => {
        const cipherListView = {
          type: { login: {} },
        } as CipherListView;

        expect(CipherViewLikeUtils.getType(cipherListView)).toBe(CipherType.Login);

        cipherListView.type = { card: { brand: "Visa" } };
        expect(CipherViewLikeUtils.getType(cipherListView)).toBe(CipherType.Card);

        cipherListView.type = "sshKey";
        expect(CipherViewLikeUtils.getType(cipherListView)).toBe(CipherType.SshKey);

        cipherListView.type = "identity";
        expect(CipherViewLikeUtils.getType(cipherListView)).toBe(CipherType.Identity);

        cipherListView.type = "secureNote";
        expect(CipherViewLikeUtils.getType(cipherListView)).toBe(CipherType.SecureNote);

        cipherListView.type = { bankAccount: { accountNumber: "1234", accountType: "checking" } };
        expect(CipherViewLikeUtils.getType(cipherListView)).toBe(CipherType.BankAccount);

        cipherListView.type = "driversLicense";
        expect(CipherViewLikeUtils.getType(cipherListView)).toBe(CipherType.DriversLicense);
      });
    });
  });

  describe("subtitle", () => {
    describe("CipherView", () => {
      it("returns the subtitle of the cipher", () => {
        const cipherView = createCipherView();
        cipherView.login = new LoginView();
        cipherView.login.username = "Test Username";

        expect(CipherViewLikeUtils.subtitle(cipherView)).toBe("Test Username");
      });
    });

    describe("CipherListView", () => {
      it("returns the subtitle of the cipher", () => {
        const cipherListView = {
          subtitle: "Test Subtitle",
          type: "identity",
        } as CipherListView;

        expect(CipherViewLikeUtils.subtitle(cipherListView)).toBe("Test Subtitle");
      });
    });

    describe("bank account with i18nService", () => {
      const i18nService = mock<I18nService>();

      beforeEach(() => {
        i18nService.t.mockReset();
        i18nService.t.mockImplementation((key: string) => `translated:${key}`);
      });

      const createBankAccountCipher = (bankAccount: BankAccountView) => {
        const cipherView = createCipherView(CipherType.BankAccount);
        cipherView.bankAccount = bankAccount;
        return cipherView;
      };

      it("returns '<translated type>, *<lastFour>' when both accountType and accountNumber are set", () => {
        const bankAccount = new BankAccountView();
        bankAccount.bankName = "Bank of America";
        bankAccount.accountType = BankAccountType.Checking;
        bankAccount.accountNumber = "987651234";

        const cipher = createBankAccountCipher(bankAccount);

        expect(CipherViewLikeUtils.subtitle(cipher, i18nService)).toBe(
          "translated:bankAccountTypeChecking, *1234",
        );
        expect(i18nService.t).toHaveBeenCalledWith("bankAccountTypeChecking");
      });

      it("returns just the translated type when accountNumber is missing", () => {
        const bankAccount = new BankAccountView();
        bankAccount.accountType = BankAccountType.Savings;

        const cipher = createBankAccountCipher(bankAccount);

        expect(CipherViewLikeUtils.subtitle(cipher, i18nService)).toBe(
          "translated:bankAccountTypeSavings",
        );
      });

      it("returns just '*<lastFour>' when accountType is missing", () => {
        const bankAccount = new BankAccountView();
        bankAccount.accountNumber = "987651234";

        const cipher = createBankAccountCipher(bankAccount);

        expect(CipherViewLikeUtils.subtitle(cipher, i18nService)).toBe("*1234");
      });

      it("falls back to the view's subTitle when both accountType and accountNumber are missing", () => {
        const bankAccount = new BankAccountView();
        bankAccount.bankName = "Bank of America";

        const cipher = createBankAccountCipher(bankAccount);

        expect(CipherViewLikeUtils.subtitle(cipher, i18nService)).toBe("Bank of America");
      });

      it("falls back to the view's subTitle when i18nService is not provided", () => {
        const bankAccount = new BankAccountView();
        bankAccount.bankName = "Bank of America";
        bankAccount.accountType = BankAccountType.Checking;
        bankAccount.accountNumber = "987651234";

        const cipher = createBankAccountCipher(bankAccount);

        expect(CipherViewLikeUtils.subtitle(cipher)).toBe("Bank of America");
        expect(i18nService.t).not.toHaveBeenCalled();
      });

      it("falls back to the SDK-provided subtitle for a CipherListView bank account without inline data", () => {
        const cipherListView = {
          type: { bankAccount: { accountNumber: undefined, accountType: undefined } },
          subtitle: "SDK-provided fallback",
        } as CipherListView;

        expect(CipherViewLikeUtils.subtitle(cipherListView, i18nService)).toBe(
          "SDK-provided fallback",
        );
      });

      it("computes a translated subtitle from the inline data of a CipherListView bank account", () => {
        const cipherListView = {
          type: {
            bankAccount: { accountNumber: "987651234", accountType: BankAccountType.Checking },
          },
          subtitle: "SDK-provided fallback",
        } as CipherListView;

        expect(CipherViewLikeUtils.subtitle(cipherListView, i18nService)).toBe(
          "translated:bankAccountTypeChecking, *1234",
        );
        expect(i18nService.t).toHaveBeenCalledWith("bankAccountTypeChecking");
      });

      it("returns the base subtitle for non-bank-account ciphers even when i18nService is provided", () => {
        const cipherView = createCipherView();
        cipherView.login = new LoginView();
        cipherView.login.username = "Test Username";

        expect(CipherViewLikeUtils.subtitle(cipherView, i18nService)).toBe("Test Username");
        expect(i18nService.t).not.toHaveBeenCalled();
      });
    });
  });

  describe("hasAttachments", () => {
    describe("CipherView", () => {
      it("returns true when the cipher has attachments", () => {
        const cipherView = createCipherView();
        cipherView.attachments = [new AttachmentView({ id: "1" } as Attachment)];

        expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(true);
      });

      it("returns false when the cipher has no attachments", () => {
        const cipherView = new CipherView();
        (cipherView.attachments as any) = null;

        expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false);

        cipherView.attachments = [];

        expect(CipherViewLikeUtils.hasAttachments(cipherView)).toBe(false);
      });
    });

    describe("CipherListView", () => {
      it("returns true when there are attachments", () => {
        const cipherListView = { attachments: 1, type: "secureNote" } as CipherListView;

        expect(CipherViewLikeUtils.hasAttachments(cipherListView)).toBe(true);
      });

      it("returns false when there are no attachments", () => {
        const cipherListView = { attachments: 0, type: "secureNote" } as CipherListView;

        expect(CipherViewLikeUtils.hasAttachments(cipherListView)).toBe(false);
      });
    });
  });

  describe("canLaunch", () => {
    it("returns false when the cipher is not a login", () => {
      const cipherView = createCipherView(CipherType.SecureNote);

      expect(CipherViewLikeUtils.canLaunch(cipherView)).toBe(false);
      expect(CipherViewLikeUtils.canLaunch({ type: "identity" } as CipherListView)).toBe(false);
    });

    describe("CipherView", () => {
      it("returns true when the login has URIs that can be launched", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();
        cipherView.login.uris = [{ uri: "https://example.com" } as LoginUriView];

        expect(CipherViewLikeUtils.canLaunch(cipherView)).toBe(true);
      });

      it("returns true when the uri does not have a protocol", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();
        const uriView = new LoginUriView();
        uriView.uri = "bitwarden.com";
        cipherView.login.uris = [uriView];

        expect(CipherViewLikeUtils.canLaunch(cipherView)).toBe(true);
      });

      it("returns false when the login has no URIs", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();

        expect(CipherViewLikeUtils.canLaunch(cipherView)).toBe(false);
      });
    });

    describe("CipherListView", () => {
      it("returns true when the login has URIs that can be launched", () => {
        const cipherListView = {
          type: { login: { uris: [{ uri: "https://example.com" }] } },
        } as CipherListView;

        expect(CipherViewLikeUtils.canLaunch(cipherListView)).toBe(true);
      });

      it("returns true when the uri does not have a protocol", () => {
        const cipherListView = {
          type: { login: { uris: [{ uri: "bitwarden.com" }] } },
        } as CipherListView;

        expect(CipherViewLikeUtils.canLaunch(cipherListView)).toBe(true);
      });

      it("returns false when the login has no URIs", () => {
        const cipherListView = { type: { login: {} } } as CipherListView;

        expect(CipherViewLikeUtils.canLaunch(cipherListView)).toBe(false);
      });
    });
  });

  describe("getLaunchUri", () => {
    it("returns undefined when the cipher is not a login", () => {
      const cipherView = createCipherView(CipherType.SecureNote);

      expect(CipherViewLikeUtils.getLaunchUri(cipherView)).toBeUndefined();
      expect(
        CipherViewLikeUtils.getLaunchUri({ type: "identity" } as CipherListView),
      ).toBeUndefined();
    });

    describe("CipherView", () => {
      it("returns the first launch-able URI", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();
        cipherView.login.uris = [
          { uri: "" } as LoginUriView,
          { uri: "https://example.com" } as LoginUriView,
          { uri: "https://another.com" } as LoginUriView,
        ];

        expect(CipherViewLikeUtils.getLaunchUri(cipherView)).toBe("https://example.com");
      });

      it("returns undefined when there are no URIs", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();

        expect(CipherViewLikeUtils.getLaunchUri(cipherView)).toBeUndefined();
      });

      it("appends protocol when there are none", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();
        const uriView = new LoginUriView();
        uriView.uri = "bitwarden.com";
        cipherView.login.uris = [uriView];

        expect(CipherViewLikeUtils.getLaunchUri(cipherView)).toBe("http://bitwarden.com");
      });
    });

    describe("CipherListView", () => {
      it("returns the first launch-able URI", () => {
        const cipherListView = {
          type: { login: { uris: [{ uri: "" }, { uri: "https://example.com" }] } },
        } as CipherListView;

        expect(CipherViewLikeUtils.getLaunchUri(cipherListView)).toBe("https://example.com");
      });

      it("returns undefined when there are no URIs", () => {
        const cipherListView = { type: { login: {} } } as CipherListView;

        expect(CipherViewLikeUtils.getLaunchUri(cipherListView)).toBeUndefined();
      });
    });
  });

  describe("matchesUri", () => {
    const emptySet = new Set<string>();

    it("returns false when the cipher is not a login", () => {
      const cipherView = createCipherView(CipherType.SecureNote);

      expect(CipherViewLikeUtils.matchesUri(cipherView, "https://example.com", emptySet)).toBe(
        false,
      );
    });

    describe("CipherView", () => {
      it("returns true when the URI matches", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();
        const uri = new LoginUriView();
        uri.uri = "https://example.com";
        cipherView.login.uris = [uri];

        expect(CipherViewLikeUtils.matchesUri(cipherView, "https://example.com", emptySet)).toBe(
          true,
        );
      });

      it("returns false when the URI does not match", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();
        const uri = new LoginUriView();
        uri.uri = "https://www.bitwarden.com";
        cipherView.login.uris = [uri];

        expect(
          CipherViewLikeUtils.matchesUri(cipherView, "https://www.another.com", emptySet),
        ).toBe(false);
      });
    });

    describe("CipherListView", () => {
      it("returns true when the URI matches", () => {
        const cipherListView = {
          type: { login: { uris: [{ uri: "https://example.com" }] } },
        } as CipherListView;

        expect(
          CipherViewLikeUtils.matchesUri(cipherListView, "https://example.com", emptySet),
        ).toBe(true);
      });

      it("returns false when the URI does not match", () => {
        const cipherListView = {
          type: { login: { uris: [{ uri: "https://bitwarden.com" }] } },
        } as CipherListView;

        expect(
          CipherViewLikeUtils.matchesUri(cipherListView, "https://another.com", emptySet),
        ).toBe(false);
      });
    });
  });

  describe("hasCopyableValue", () => {
    describe("CipherView", () => {
      it("returns true for login fields", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();
        cipherView.login.username = "testuser";
        cipherView.login.password = "testpass";

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "username")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "password")).toBe(true);
      });

      it("returns true for card fields", () => {
        const cipherView = createCipherView(CipherType.Card);
        cipherView.card = { number: "1234-5678-9012-3456", code: "123" } as any;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "cardNumber")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "securityCode")).toBe(true);
      });

      it("returns true for identity fields", () => {
        const cipherView = createCipherView(CipherType.Identity);
        cipherView.identity = new IdentityView();
        cipherView.identity.email = "example@bitwarden.com";
        cipherView.identity.phone = "123-456-7890";

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "email")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "phone")).toBe(true);
      });

      it("returns true for bank account fields", () => {
        const cipherView = createCipherView(CipherType.BankAccount);
        cipherView.bankAccount.nameOnAccount = "Jane Doe";
        cipherView.bankAccount.accountNumber = "123456789";
        cipherView.bankAccount.routingNumber = "987654321";
        cipherView.bankAccount.branchNumber = "001";
        cipherView.bankAccount.pin = "1234";
        cipherView.bankAccount.iban = "GB29NWBK60161331926819";
        cipherView.bankAccount.swiftCode = "BOFAUS3N";

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "nameOnAccount")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "accountNumber")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "routingNumber")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "branchNumber")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "pin")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "iban")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "swiftCode")).toBe(true);
      });

      it("returns false when values are not populated", () => {
        const cipherView = createCipherView(CipherType.Login);

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "email")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "password")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "securityCode")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "username")).toBe(false);
      });

      it("returns false for bank account fields when not populated", () => {
        const cipherView = createCipherView(CipherType.BankAccount);

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "nameOnAccount")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "accountNumber")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "routingNumber")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "branchNumber")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "pin")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "iban")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "swiftCode")).toBe(false);
      });

      it("returns true for drivers license name and license number fields when populated", () => {
        const cipherView = createCipherView(CipherType.DriversLicense);
        cipherView.driversLicense.firstName = "Jane";
        cipherView.driversLicense.middleName = "Q";
        cipherView.driversLicense.lastName = "Doe";
        cipherView.driversLicense.licenseNumber = "D1234567";

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "firstNameLicense")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "middleNameLicense")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "lastNameLicense")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "licenseNumber")).toBe(true);
      });

      it("returns false for drivers license fields when not populated", () => {
        const cipherView = createCipherView(CipherType.DriversLicense);

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "firstNameLicense")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "middleNameLicense")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "lastNameLicense")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "licenseNumber")).toBe(false);
      });

      it("returns true for passport name, number, and nid fields when populated", () => {
        const cipherView = createCipherView(CipherType.Passport);
        cipherView.passport.givenName = "Jane";
        cipherView.passport.surname = "Doe";
        cipherView.passport.passportNumber = "P12345678";
        cipherView.passport.nationalIdentificationNumber = "NID-001";

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "givenName")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "surname")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "passportNumber")).toBe(true);
        expect(
          CipherViewLikeUtils.hasCopyableValue(cipherView, "nationalIdentificationNumber"),
        ).toBe(true);
        // Passport has no middleName.
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "middleNamePassport")).toBe(false);
      });

      it("returns false for passport fields when not populated", () => {
        const cipherView = createCipherView(CipherType.Passport);

        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "givenName")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "surname")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherView, "passportNumber")).toBe(false);
        expect(
          CipherViewLikeUtils.hasCopyableValue(cipherView, "nationalIdentificationNumber"),
        ).toBe(false);
      });
    });

    describe("CipherListView", () => {
      it("returns true for copyable fields in a login cipher", () => {
        const cipherListView = {
          type: { login: { username: "testuser" } },
          copyableFields: ["LoginUsername", "LoginPassword"],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "username")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "password")).toBe(true);
      });

      it("returns true for copyable fields in a card cipher", () => {
        const cipherListView = {
          type: { card: { brand: "MasterCard" } },
          copyableFields: ["CardNumber", "CardSecurityCode"],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "cardNumber")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "securityCode")).toBe(true);
      });

      it("returns true for copyable fields in an sshKey ciphers", () => {
        const cipherListView = {
          type: "sshKey",
          copyableFields: ["SshKey"],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "privateKey")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "publicKey")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "keyFingerprint")).toBe(true);
      });

      it("returns true for copyable fields in an identity cipher", () => {
        const cipherListView = {
          type: "identity",
          copyableFields: ["IdentityUsername", "IdentityEmail", "IdentityPhone"],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "username")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "email")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "phone")).toBe(true);
      });

      it("returns true for copyable fields in a bank account cipher", () => {
        const cipherListView = {
          copyableFields: [
            "BankAccountNameOnAccount",
            "BankAccountAccountNumber",
            "BankAccountRoutingNumber",
            "BankAccountBranchNumber",
            "BankAccountPin",
            "BankAccountIban",
            "BankAccountSwift",
          ],
          type: { bankAccount: { accountNumber: undefined, accountType: undefined } },
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "nameOnAccount")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "accountNumber")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "routingNumber")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "branchNumber")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "pin")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "iban")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "swiftCode")).toBe(true);
      });

      it("returns true for copyable fields in a drivers license cipher", () => {
        const cipherListView = {
          type: "driversLicense",
          copyableFields: [
            "DriversLicenseFirstName",
            "DriversLicenseMiddleName",
            "DriversLicenseLastName",
            "DriversLicenseLicenseNumber",
          ],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "firstNameLicense")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "middleNameLicense")).toBe(
          true,
        );
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "lastNameLicense")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "licenseNumber")).toBe(true);
      });

      it("returns true for copyable fields in a passport cipher", () => {
        const cipherListView = {
          type: "passport",
          copyableFields: [
            "PassportGivenName",
            "PassportSurname",
            "PassportPassportNumber",
            "PassportNationalIdentificationNumber",
          ],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "givenName")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "surname")).toBe(true);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "passportNumber")).toBe(true);
        expect(
          CipherViewLikeUtils.hasCopyableValue(cipherListView, "nationalIdentificationNumber"),
        ).toBe(true);
        // Passport has no middleName, even if a drivers-license middleName SDK field were present.
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "middleName")).toBe(false);
      });

      it("returns false for when missing a field", () => {
        const cipherListView = {
          type: { login: {} },
          copyableFields: ["LoginUsername"],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "password")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "phone")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "address")).toBe(false);
        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "publicKey")).toBe(false);
      });

      it("returns false for login username when copyableFields includes it but the value is empty", () => {
        const cipherListView = {
          type: { login: { username: "" } },
          copyableFields: ["LoginUsername"],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "username")).toBe(false);
      });

      it("returns false for login username when copyableFields includes it but the value is undefined", () => {
        const cipherListView = {
          type: { login: { username: undefined } },
          copyableFields: ["LoginUsername"],
        } as CipherListView;

        expect(CipherViewLikeUtils.hasCopyableValue(cipherListView, "username")).toBe(false);
      });
    });
  });

  describe("hasFido2Credentials", () => {
    describe("CipherView", () => {
      it("returns true when the login has FIDO2 credentials", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();
        cipherView.login.fido2Credentials = [new Fido2CredentialView()];

        expect(CipherViewLikeUtils.hasFido2Credentials(cipherView)).toBe(true);
      });

      it("returns false when the login has no FIDO2 credentials", () => {
        const cipherView = createCipherView(CipherType.Login);
        cipherView.login = new LoginView();

        expect(CipherViewLikeUtils.hasFido2Credentials(cipherView)).toBe(false);
      });
    });

    describe("CipherListView", () => {
      it("returns true when the login has FIDO2 credentials", () => {
        const cipherListView = {
          type: { login: { fido2Credentials: [{ credentialId: "fido2-1" }] } },
        } as CipherListView;

        expect(CipherViewLikeUtils.hasFido2Credentials(cipherListView)).toBe(true);
      });

      it("returns false when the login has no FIDO2 credentials", () => {
        const cipherListView = { type: { login: {} } } as CipherListView;

        expect(CipherViewLikeUtils.hasFido2Credentials(cipherListView)).toBe(false);
      });
    });
  });

  describe("decryptionFailure", () => {
    it("returns true when the cipher has a decryption failure", () => {
      const cipherView = createCipherView();
      cipherView.decryptionFailure = true;

      expect(CipherViewLikeUtils.decryptionFailure(cipherView)).toBe(true);
    });

    it("returns false when the cipher does not have a decryption failure", () => {
      const cipherView = createCipherView();
      cipherView.decryptionFailure = false;

      expect(CipherViewLikeUtils.decryptionFailure(cipherView)).toBe(false);
    });

    it("returns false when the cipher is a CipherListView without decryptionFailure", () => {
      const cipherListView = { type: "secureNote" } as CipherListView;

      expect(CipherViewLikeUtils.decryptionFailure(cipherListView)).toBe(false);
    });
  });

  describe("getNotes", () => {
    describe("CipherView", () => {
      it("returns notes when present", () => {
        const cipherView = createCipherView();
        cipherView.notes = "This is a test note";

        expect(CipherViewLikeUtils.getNotes(cipherView)).toBe("This is a test note");
      });

      it("returns undefined when notes are not present", () => {
        const cipherView = createCipherView();
        cipherView.notes = undefined;

        expect(CipherViewLikeUtils.getNotes(cipherView)).toBeUndefined();
      });
    });

    describe("CipherListView", () => {
      it("returns notes when present", () => {
        const cipherListView = {
          type: "secureNote",
          notes: "List view notes",
        } as CipherListView;

        expect(CipherViewLikeUtils.getNotes(cipherListView)).toBe("List view notes");
      });

      it("returns undefined when notes are not present", () => {
        const cipherListView = {
          type: "secureNote",
        } as CipherListView;

        expect(CipherViewLikeUtils.getNotes(cipherListView)).toBeUndefined();
      });
    });
  });

  describe("getFields", () => {
    describe("CipherView", () => {
      it("returns fields when present", () => {
        const cipherView = createCipherView();
        cipherView.fields = [
          { name: "Field1", value: "Value1" } as any,
          { name: "Field2", value: "Value2" } as any,
        ];

        const fields = CipherViewLikeUtils.getFields(cipherView);

        expect(fields).toHaveLength(2);
        expect(fields?.[0].name).toBe("Field1");
        expect(fields?.[0].value).toBe("Value1");
        expect(fields?.[1].name).toBe("Field2");
        expect(fields?.[1].value).toBe("Value2");
      });

      it("returns empty array when fields array is empty", () => {
        const cipherView = createCipherView();
        cipherView.fields = [];

        expect(CipherViewLikeUtils.getFields(cipherView)).toEqual([]);
      });
    });

    describe("CipherListView", () => {
      it("returns fields when present", () => {
        const cipherListView = {
          type: { login: {} },
          fields: [
            { name: "Username", value: "user@example.com" },
            { name: "API Key", value: "abc123" },
          ],
        } as CipherListView;

        const fields = CipherViewLikeUtils.getFields(cipherListView);

        expect(fields).toHaveLength(2);
        expect(fields?.[0].name).toBe("Username");
        expect(fields?.[0].value).toBe("user@example.com");
        expect(fields?.[1].name).toBe("API Key");
        expect(fields?.[1].value).toBe("abc123");
      });

      it("returns empty array when fields array is empty", () => {
        const cipherListView = {
          type: "secureNote",
          fields: [],
        } as unknown as CipherListView;

        expect(CipherViewLikeUtils.getFields(cipherListView)).toEqual([]);
      });

      it("returns undefined when fields are not present", () => {
        const cipherListView = {
          type: "secureNote",
        } as CipherListView;

        expect(CipherViewLikeUtils.getFields(cipherListView)).toBeUndefined();
      });
    });
  });

  describe("getAttachmentNames", () => {
    describe("CipherView", () => {
      it("returns attachment filenames when present", () => {
        const cipherView = createCipherView();
        const attachment1 = new AttachmentView();
        attachment1.id = "1";
        attachment1.fileName = "document.pdf";
        const attachment2 = new AttachmentView();
        attachment2.id = "2";
        attachment2.fileName = "image.png";
        const attachment3 = new AttachmentView();
        attachment3.id = "3";
        attachment3.fileName = "spreadsheet.xlsx";
        cipherView.attachments = [attachment1, attachment2, attachment3];

        const attachmentNames = CipherViewLikeUtils.getAttachmentNames(cipherView);

        expect(attachmentNames).toEqual(["document.pdf", "image.png", "spreadsheet.xlsx"]);
      });

      it("filters out null and undefined filenames", () => {
        const cipherView = createCipherView();
        const attachment1 = new AttachmentView();
        attachment1.id = "1";
        attachment1.fileName = "valid.pdf";
        const attachment2 = new AttachmentView();
        attachment2.id = "2";
        attachment2.fileName = null as any;
        const attachment3 = new AttachmentView();
        attachment3.id = "3";
        attachment3.fileName = undefined;
        const attachment4 = new AttachmentView();
        attachment4.id = "4";
        attachment4.fileName = "another.txt";
        cipherView.attachments = [attachment1, attachment2, attachment3, attachment4];

        const attachmentNames = CipherViewLikeUtils.getAttachmentNames(cipherView);

        expect(attachmentNames).toEqual(["valid.pdf", "another.txt"]);
      });

      it("returns empty array when attachments have no filenames", () => {
        const cipherView = createCipherView();
        const attachment1 = new AttachmentView();
        attachment1.id = "1";
        const attachment2 = new AttachmentView();
        attachment2.id = "2";
        cipherView.attachments = [attachment1, attachment2];

        const attachmentNames = CipherViewLikeUtils.getAttachmentNames(cipherView);

        expect(attachmentNames).toEqual([]);
      });

      it("returns empty array for empty attachments array", () => {
        const cipherView = createCipherView();
        cipherView.attachments = [];

        expect(CipherViewLikeUtils.getAttachmentNames(cipherView)).toEqual([]);
      });
    });

    describe("CipherListView", () => {
      it("returns attachment names when present", () => {
        const cipherListView = {
          type: "secureNote",
          attachmentNames: ["report.pdf", "photo.jpg", "data.csv"],
        } as CipherListView;

        const attachmentNames = CipherViewLikeUtils.getAttachmentNames(cipherListView);

        expect(attachmentNames).toEqual(["report.pdf", "photo.jpg", "data.csv"]);
      });

      it("returns empty array when attachmentNames is empty", () => {
        const cipherListView = {
          type: "secureNote",
          attachmentNames: [],
        } as unknown as CipherListView;

        expect(CipherViewLikeUtils.getAttachmentNames(cipherListView)).toEqual([]);
      });

      it("returns undefined when attachmentNames is not present", () => {
        const cipherListView = {
          type: "secureNote",
        } as CipherListView;

        expect(CipherViewLikeUtils.getAttachmentNames(cipherListView)).toBeUndefined();
      });
    });
  });
});
