import { describe, expect, it } from "vitest";
import { checkPasswordPolicy } from "../security/password-policy.js";

describe("checkPasswordPolicy", () => {
  describe("length bounds", () => {
    it("rejects passwords shorter than 12 chars", () => {
      const result = checkPasswordPolicy("short1!");
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/at least 12/i);
    });

    it("rejects passwords longer than 128 chars", () => {
      const result = checkPasswordPolicy("A".repeat(129));
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/no more than 128/i);
    });

    it("accepts passwords at the lower bound that pass other checks", () => {
      const result = checkPasswordPolicy("Tr0ub4dor&3!"); // 12 chars, varied
      expect(result.ok).toBe(true);
    });
  });

  describe("trivial patterns", () => {
    it("rejects single char repeated", () => {
      const result = checkPasswordPolicy("aaaaaaaaaaaa");
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/single character/i);
    });

    it("rejects short patterns repeated", () => {
      const result = checkPasswordPolicy("abcabcabcabc");
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/short pattern/i);
    });

    it("rejects '1212121212121212' (2-char pattern repeated)", () => {
      const result = checkPasswordPolicy("1212121212121212");
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/short pattern/i);
    });

    it("rejects keyboard sequences like 'abcdefghijkl'", () => {
      const result = checkPasswordPolicy("abcdefghijkl");
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/sequence/i);
    });

    it("rejects numeric runs like '1234567890ab'", () => {
      const result = checkPasswordPolicy("1234567890ab");
      // Either matches blacklist or sequence detector — both rejections OK.
      expect(result.ok).toBe(false);
    });
  });

  describe("common-password blacklist", () => {
    it("rejects 'PasswordPassword' (case-insensitive)", () => {
      const result = checkPasswordPolicy("PasswordPassword");
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/commonly used/i);
    });

    it("rejects 'Welcome2026!' seasonal password", () => {
      const result = checkPasswordPolicy("Welcome2026!");
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/commonly used/i);
    });

    it("rejects 'Summer2025!' regardless of case", () => {
      const result = checkPasswordPolicy("SUMMER2025!");
      expect(result.ok).toBe(false);
    });
  });

  describe("identity inclusion", () => {
    it("rejects passwords containing the email local-part", () => {
      const result = checkPasswordPolicy("hartmutSomePass1", {
        email: "hartmut@example.com",
      });
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/email or name/i);
    });

    it("rejects passwords containing the user name", () => {
      const result = checkPasswordPolicy("MyHartmutPass1!", {
        name: "Hartmut Noerenberg",
      });
      expect(result.ok).toBe(false);
      if (!result.ok) expect(result.reason).toMatch(/email or name/i);
    });

    it("ignores short email locals to avoid false positives", () => {
      const result = checkPasswordPolicy("XyZmagic12345!", { email: "x@example.com" });
      expect(result.ok).toBe(true);
    });

    it("ignores short names (<4 chars)", () => {
      const result = checkPasswordPolicy("XyZmagic12345!", { name: "Ed" });
      expect(result.ok).toBe(true);
    });
  });

  describe("strong passwords", () => {
    it("accepts a 16-char random-looking passphrase", () => {
      const result = checkPasswordPolicy("Tr0ub4d0r&3-x9Q!");
      expect(result.ok).toBe(true);
    });

    it("accepts diceware-style passphrase", () => {
      const result = checkPasswordPolicy("correct-horse-battery-staple-7!");
      expect(result.ok).toBe(true);
    });
  });
});
