import { Alepha } from "alepha";
import { describe, expect, it } from "vitest";
import { InvalidPermissionError, SecurityProvider } from "../index.ts";

/**
 * Bug #3: Regex Vulnerability in PERMISSION_REGEXP_WILDCARD
 *
 * Issue: The regex had incorrect operator precedence:
 * OLD: /^[\w-]+((:[\w-]+)+)?|(:\*)$/
 * This created two patterns: /^[\w-]+((:[\w-]+)+)?/ OR /(:\*)$/
 * The second part matched ":*" anywhere without start anchor.
 *
 * FIXED: /^[\w-]+((:[\w-]+)*:\*|(:[\w-]+)+)?$/
 * Now correctly matches: start, then word chars/hyphens, then optionally either:
 * - (zero or more groups):* (for wildcards like "admin:api:*")
 * - (one or more groups) (for exact permissions like "users:read")
 * - nothing (for simple permissions like "read")
 *
 * Expected: Invalid patterns like standalone ":*" should be rejected.
 */
describe("Bug #3: Regex Vulnerability in PERMISSION_REGEXP_WILDCARD", () => {
  describe("Valid wildcard patterns (should pass)", () => {
    it("should accept global wildcard", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      // Global wildcard is handled separately, but still valid
      expect(() =>
        sec.createRole({
          name: "admin",
          permissions: [{ name: "*" }],
        }),
      ).not.toThrow();
    });

    it("should accept simple wildcard", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "user",
          permissions: [{ name: "users:*" }],
        }),
      ).not.toThrow();
    });

    it("should accept multi-layer wildcard", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "admin",
          permissions: [
            { name: "admin:api:*" },
            { name: "admin:api:v1:*" },
            { name: "admin:api:v1:users:*" },
          ],
        }),
      ).not.toThrow();
    });

    it("should accept regular permissions with groups", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "user",
          permissions: [
            { name: "users:read" },
            { name: "admin:api:users:read" },
            { name: "a:b:c:d:e:f" },
          ],
        }),
      ).not.toThrow();
    });

    it("should accept permissions with hyphens", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "user",
          permissions: [
            { name: "user-management:*" },
            { name: "api-v1:users:*" },
            { name: "admin-portal:api-gateway:*" },
          ],
        }),
      ).not.toThrow();
    });

    it("should accept permissions with underscores", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "user",
          permissions: [
            { name: "user_management:*" },
            { name: "api_v1:users:*" },
          ],
        }),
      ).not.toThrow();
    });
  });

  describe("Invalid wildcard patterns (should be rejected)", () => {
    it("should reject standalone :*", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      // This was the bug - old regex accepted ":*" without prefix
      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: ":*" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject pattern starting with colon", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: ":users" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject pattern ending with colon (no wildcard)", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "users:" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject pattern with double colons", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "users::read" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject empty string", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject just a colon", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: ":" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject special characters", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      const invalidChars = [
        "users@admin:*",
        "users#read",
        "users.read:*",
        "users/read",
        "users read", // space
        "users!:*",
        "admin$:*",
        "api%:users",
      ];

      for (const invalid of invalidChars) {
        expect(() =>
          sec.createRole({
            name: "invalid",
            permissions: [{ name: invalid }],
          }),
        ).toThrow(InvalidPermissionError);
      }
    });

    it("should reject pattern with multiple wildcards", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "users:*:*" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject wildcard not at the end", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "users:*:read" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject pattern ending with multiple colons", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "users::" }],
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should reject pattern starting with multiple colons", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "::users" }],
        }),
      ).toThrow(InvalidPermissionError);
    });
  });

  describe("Edge cases for regex validation", () => {
    it("should accept numbers in permission names", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      expect(() =>
        sec.createRole({
          name: "user",
          permissions: [
            { name: "api123:*" },
            { name: "v1:users:*" },
            { name: "resource2024:read" },
          ],
        }),
      ).not.toThrow();
    });

    it("should accept very long permission names", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      const longPart = "a".repeat(100);
      expect(() =>
        sec.createRole({
          name: "user",
          permissions: [{ name: `${longPart}:*` }],
        }),
      ).not.toThrow();
    });

    it("should accept deeply nested permissions", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      const deep = Array(20).fill("level").join(":");
      expect(() =>
        sec.createRole({
          name: "user",
          permissions: [{ name: `${deep}:*` }],
        }),
      ).not.toThrow();
    });

    it("should properly validate permission objects (not just strings)", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      // When creating permissions from objects, they go through the same validation
      expect(() => sec.createPermission({ name: ":invalid" })).toThrow(
        InvalidPermissionError,
      );

      expect(() => sec.createPermission({ name: "users:" })).toThrow(
        InvalidPermissionError,
      );

      expect(() => sec.createPermission({ name: "users::read" })).toThrow(
        InvalidPermissionError,
      );
    });

    it("should validate wildcards only appear in role permissions, not created permissions", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      // You cannot create a permission with a wildcard
      expect(() => sec.createPermission("users:*")).toThrow(
        InvalidPermissionError,
      );

      // But you can use wildcards in roles
      sec.createPermission("users:read");
      expect(() =>
        sec.createRole({
          name: "user",
          permissions: [{ name: "users:*" }],
        }),
      ).not.toThrow();
    });
  });

  describe("Regex security validation", () => {
    it("should ensure start anchor is enforced", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      // These should fail because they don't start with valid chars
      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: " users:*" }], // leading space
        }),
      ).toThrow(InvalidPermissionError);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "\nusers:*" }], // leading newline
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should ensure end anchor is enforced", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      // These should fail because they don't end properly
      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "users:* " }], // trailing space
        }),
      ).toThrow(InvalidPermissionError);

      expect(() =>
        sec.createRole({
          name: "invalid",
          permissions: [{ name: "users:*\n" }], // trailing newline
        }),
      ).toThrow(InvalidPermissionError);
    });

    it("should not allow regex injection attempts", () => {
      const app = Alepha.create();
      const sec = app.inject(SecurityProvider);

      const injectionAttempts = [
        "users.*:*", // regex wildcard
        "users[a-z]:*", // regex character class
        "users|admin:*", // regex alternation
        "users(admin):*", // regex group
        "users+:*", // regex quantifier
        "users?:*", // regex quantifier
        "users^:*", // regex anchor
        "users$:*", // regex anchor
      ];

      for (const attempt of injectionAttempts) {
        expect(() =>
          sec.createRole({
            name: "invalid",
            permissions: [{ name: attempt }],
          }),
        ).toThrow(InvalidPermissionError);
      }
    });
  });
});
