import {
  $hook,
  $inject,
  Alepha,
  AppNotStartedError,
  ContainerLockedError,
} from "alepha";
import { SecretProvider } from "alepha/crypto";
import { $logger } from "alepha/logger";
import { ForbiddenError } from "alepha/server";
import type { JSONWebKeySet, JWTPayload } from "jose";
import type { JWTVerifyOptions } from "jose/jwt/verify";
import { currentTenantAtom } from "../atoms/currentTenantAtom.ts";
import { currentUserAtom } from "../atoms/currentUserAtom.ts";
import { InvalidPermissionError } from "../errors/InvalidPermissionError.ts";
import { InvalidTokenError } from "../errors/InvalidTokenError.ts";
import { RealmNotFoundError } from "../errors/RealmNotFoundError.ts";
import { SecurityError } from "../errors/SecurityError.ts";
import type { IssuerResolver, UserInfo } from "../interfaces/IssuerResolver.ts";
import type { UserAccountToken } from "../interfaces/UserAccountToken.ts";
import type { Permission } from "../schemas/permissionSchema.ts";
import type { Role } from "../schemas/roleSchema.ts";
import {
  type UserAccount,
  userAccountInfoSchema,
} from "../schemas/userAccountInfoSchema.ts";
import { JwtProvider, type SigningConfig } from "./JwtProvider.ts";

export class SecurityProvider {
  protected readonly UNKNOWN_USER_NAME = "Anonymous User";
  protected readonly PERMISSION_REGEXP = /^[\w-]+((:[\w-]+)+)?$/;
  protected readonly PERMISSION_REGEXP_WILDCARD =
    /^[\w-]+((:[\w-]+)*:\*|(:[\w-]+)+)?$/;

  protected readonly log = $logger();
  protected readonly jwt = $inject(JwtProvider);
  protected readonly alepha = $inject(Alepha);
  protected readonly secretProvider = $inject(SecretProvider);

  public get secretKey(): string {
    return this.secretProvider.secretKey;
  }

  /**
   * The permissions configured for the security provider.
   */
  protected readonly permissions: Permission[] = [];

  /**
   * The realms configured for the security provider.
   */
  protected readonly realms: Realm[] = this.alepha.isTest()
    ? [
        {
          name: "default",
          secret: this.secretKey,
          roles: [
            {
              name: "admin",
              permissions: [
                {
                  name: "*",
                },
              ],
            },
          ],
        },
      ]
    : [];

  protected start = $hook({
    on: "start",
    handler: async () => {
      for (const realm of this.realms) {
        if (realm.signing) {
          await this.jwt.setSigningKey(realm.name, realm.signing);
        } else if (realm.secret) {
          const secret =
            typeof realm.secret === "function" ? realm.secret() : realm.secret;
          this.jwt.setKeyLoader(realm.name, secret);
        }

        // Register default JWT resolver for realms without resolvers
        if (!realm.resolvers || realm.resolvers.length === 0) {
          this.registerResolver(
            this.createDefaultJwtResolver(realm.name),
            realm.name,
          );
        }
      }
    },
  });

  /**
   * Creates a default JWT resolver for a realm.
   */
  protected createDefaultJwtResolver(realmName: string): IssuerResolver {
    return {
      priority: 100,
      onRequest: async (req) => {
        const auth = req.headers.authorization;
        if (!auth?.startsWith("Bearer ")) {
          return null;
        }

        const token = auth.slice(7);

        // Check if it looks like a JWT (has dots)
        if (!token.includes(".")) {
          return null;
        }

        // Parse and validate JWT
        const { result } = await this.jwt.parse(token, realmName);

        // Reject tokens whose tenant claim doesn't match the tenant resolved
        // for the current request. Prevents a token minted on tenant A from
        // being replayed on tenant B (subdomain spoofing, leaked bearer
        // tokens). Tokens minted without a tenant claim (no active tenant at
        // session creation) pass through — single-tenant apps are unaffected.
        const claimTenant = this.getTenantFromPayload(result.payload);
        if (claimTenant) {
          const activeTenant = this.alepha.store.get(currentTenantAtom)?.id;
          if (activeTenant && activeTenant !== claimTenant) {
            this.log.warn("JWT tenant claim does not match active tenant", {
              claim: claimTenant,
              active: activeTenant,
            });
            return null;
          }
        }

        // Extract user info from JWT payload
        return this.createUserFromPayload(result.payload, realmName);
      },
    };
  }

  /**
   * Adds a role to one or more realms.
   *
   * @param role
   * @param realms
   */
  public createRole(role: Role, ...realms: string[]): Role {
    const list = realms.length
      ? realms.map((it) => {
          const item = this.realms.find((realm) => realm.name === it);
          if (!item) {
            throw new RealmNotFoundError(it);
          }
          return item;
        })
      : this.realms;

    for (const realm of list) {
      for (const { name } of role.permissions) {
        if (this.alepha.isStarted()) {
          // Check if permission exists or matches a wildcard pattern
          if (name === "*") {
            // Global wildcard is always allowed
            continue;
          }

          // Check for exact match first
          const existingExact = this.permissions.find(
            (it) => this.permissionToString(it) === name,
          );
          if (existingExact) {
            continue;
          }

          // Check if it's a wildcard pattern (e.g., "admin:api:*")
          if (name.endsWith(":*")) {
            const groupPrefix = name.slice(0, -2); // Remove ":*"
            // Check if any permission exists with this group prefix
            const existingWithPrefix = this.permissions.find((it) => {
              if (!it.group) return false;
              return (
                it.group === groupPrefix ||
                it.group.startsWith(`${groupPrefix}:`)
              );
            });
            if (existingWithPrefix) {
              continue;
            }
          }

          // Permission not found
          throw new SecurityError(`Permission '${name}' not found`);
        } else {
          if (name !== "*" && !this.PERMISSION_REGEXP_WILDCARD.test(name)) {
            throw new InvalidPermissionError(name);
          }
        }
      }

      realm.roles.push(role);
    }

    return role;
  }

  /**
   * Adds a permission to the security provider.
   *
   * @param raw - The permission to add.
   */
  public createPermission(raw: Permission | string): Permission {
    if (this.alepha.isStarted()) {
      throw new ContainerLockedError();
    }

    let permission: Permission;
    if (typeof raw === "string") {
      if (!this.PERMISSION_REGEXP.test(raw)) {
        throw new InvalidPermissionError(raw);
      }

      const parts = raw.split(":");
      if (parts.length === 1) {
        // No group, just name (e.g., "read")
        permission = { name: parts[0] };
      } else {
        // Has group(s) (e.g., "users:read" or "admin:api:users:read")
        // The last part is the name, everything else is the group
        const name = parts[parts.length - 1];
        const groupParts = parts.slice(0, -1);

        if (groupParts.length === 1) {
          permission = {
            group: groupParts[0],
            name,
          };
        } else {
          // Multi-layer group
          permission = {
            group: groupParts.join(":"),
            name,
          };
        }
      }
    } else {
      permission = raw;
    }

    const asString = this.permissionToString(permission);
    if (!this.PERMISSION_REGEXP.test(asString)) {
      throw new InvalidPermissionError(asString);
    }

    const existing = this.permissions.find(
      (it) => this.permissionToString(it) === asString,
    );

    if (existing) {
      return existing;
    }

    this.log.trace(`Creating permission '${asString}'`);

    this.permissions.push(permission);

    return permission;
  }

  public createRealm(realm: Realm) {
    if (this.realms.length === 1 && this.realms[0].name === "default") {
      // if the default realm is the only one, we remove it to allow creating new realms
      this.realms.pop();
    }

    this.realms.push(realm);
  }

  /**
   * Updates the roles for a realm then synchronizes the user account provider if available.
   *
   * Only available when the app is started.
   *
   * @param realm - The realm to update the roles for.
   * @param roles - The roles to update.
   */
  public async updateRealm(realm: string, roles: Role[]): Promise<void> {
    if (!this.alepha.isStarted()) {
      throw new AppNotStartedError();
    }

    const realmInstance = this.realms.find((it) => it.name === realm);
    if (!realmInstance) {
      throw new RealmNotFoundError(realm);
    }

    realmInstance.roles = roles;
  }

  // -------------------------------------------------------------------------------------------------------------------

  /**
   * Creates a user account from the provided payload.
   *
   * @param payload - The payload to create the user account from.
   * @param [realmName] - The realm containing the roles. Default is all.
   *
   * @returns The user info created from the payload.
   */
  public createUserFromPayload(
    payload: JWTPayload,
    realmName?: string,
  ): UserAccount {
    const id = this.getIdFromPayload(payload);
    const sessionId = this.getSessionIdFromPayload(payload);
    const rolesFromPayload = this.getRolesFromPayload(payload);
    const email = this.getEmailFromPayload(payload);
    const username = this.getUsernameFromPayload(payload);
    const picture = this.getPictureFromPayload(payload);
    const name = this.getNameFromPayload(payload);
    const firstName =
      typeof payload.given_name === "string" ? payload.given_name : undefined;
    const lastName =
      typeof payload.family_name === "string" ? payload.family_name : undefined;
    const organization = this.getOrganizationFromPayload(payload);
    const rolesFromSystem = this.getRoles(realmName);
    const roles = rolesFromPayload
      .reduce<Role[]>(
        (arr, roleName) =>
          arr.concat(rolesFromSystem.filter((it) => it.name === roleName)),
        [],
      )
      .map((it) => it.name);

    const realm = this.realms.find((it) => it.name === realmName);
    if (realm?.profile) {
      return realm.profile(payload);
    }

    return {
      id,
      roles,
      name,
      firstName,
      lastName,
      email,
      username,
      picture,
      organization,
      sessionId,
    };
  }

  /**
   * Generic user creation from any source (JWT, API key, etc.).
   * Handles permission checking, ownership, default roles.
   */
  public createUser(
    userInfo: UserInfo,
    options: {
      realm?: string;
      permission?: Permission | string;
    } = {},
  ): UserAccountToken {
    const realmRoles = this.getRoles(options.realm).filter((it) => it.default);
    const roles = [...(userInfo.roles ?? [])];

    // Add default roles
    for (const role of realmRoles) {
      if (!roles.includes(role.name)) {
        roles.push(role.name);
      }
    }

    let ownership: string | boolean | undefined;

    // Permission check
    if (options.permission) {
      const check = this.checkPermission(options.permission, ...roles);
      if (!check.isAuthorized) {
        throw new SecurityError(
          `User is not allowed to access '${this.permissionToString(options.permission)}'`,
        );
      }
      ownership = check.ownership;
    }

    return {
      ...userInfo,
      roles,
      ownership,
      realm: options.realm,
    };
  }

  /**
   * Register a resolver to a realm.
   * Resolvers are sorted by priority (lower = first).
   */
  public registerResolver(resolver: IssuerResolver, realmName?: string): void {
    const realm = this.getRealm(realmName);
    if (!realm.resolvers) {
      realm.resolvers = [];
    }

    realm.resolvers.push(resolver);
    realm.resolvers.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
  }

  /**
   * Get a realm by name.
   * Throws if realm not found.
   */
  public getRealm(realmName?: string): Realm {
    const realm = realmName
      ? this.realms.find((it) => it.name === realmName)
      : this.realms[0];

    if (!realm) {
      throw new RealmNotFoundError(realmName ?? "default");
    }

    return realm;
  }

  /**
   * Resolve user from request using registered resolvers.
   * Returns undefined if no resolver could authenticate (no auth provided).
   * Throws UnauthorizedError if auth was provided but invalid.
   *
   * Note: This method tries resolvers from ALL realms to find a match,
   * regardless of the `realm` option. The `realm` option is only used for
   * permission checking after the user is resolved.
   */
  public async resolveUserFromServerRequest(
    req: { url: URL | string; headers: { authorization?: string } },
    options: {
      realm?: string;
      permission?: Permission | string;
    } = {},
  ): Promise<UserAccountToken | undefined> {
    // Collect all resolvers from all realms with their realm name
    const allResolvers: Array<{
      resolver: IssuerResolver;
      realmName: string;
    }> = [];

    for (const realm of this.realms) {
      for (const resolver of realm.resolvers ?? []) {
        allResolvers.push({ resolver, realmName: realm.name });
      }
    }

    // Sort by priority
    allResolvers.sort(
      (a, b) => (a.resolver.priority ?? 100) - (b.resolver.priority ?? 100),
    );

    // Try resolvers in priority order
    for (const { resolver, realmName } of allResolvers) {
      let userInfo: UserInfo | null;

      try {
        userInfo = await resolver.onRequest(req as any);
      } catch {
        // Resolver failed (e.g., wrong key), try next
        continue;
      }

      if (userInfo) {
        // User was resolved - now create user and check permissions
        // (errors from createUser should propagate, not be caught)
        const user = this.createUser(userInfo, {
          realm: realmName,
          permission: options.permission,
        });

        await this.alepha.events.emit("security:user:created", {
          realm: realmName,
          user,
        });

        return user;
      }
    }

    // No resolver matched = no auth provided
    return undefined;
  }

  /**
   * Checks if the user has the specified permission.
   *
   * Bonus: we check also if the user has "ownership" flag.
   *
   * @param permissionLike - The permission to check for.
   * @param roleEntries - The roles to check for the permission.
   */
  public checkPermission(
    permissionLike: string | Permission,
    ...roleEntries: string[]
  ): SecurityCheckResult {
    const roles: Role[] = roleEntries.map((it) => {
      const role = this.getRoles().find((role) => role.name === it);
      if (!role) {
        throw new SecurityError(`Role '${it}' not found`);
      }
      return role;
    });

    const permission = this.permissionToString(permissionLike);
    const isAdmin = roles.find((it) =>
      it.permissions.find(
        (it) => it.name === "*" && !it.exclude && !it.ownership,
      ),
    );

    // if the user is an admin, we can return early
    if (isAdmin) {
      return {
        isAuthorized: true,
        ownership: false,
      };
    }

    const result: SecurityCheckResult = {
      isAuthorized: false,
      ownership: undefined,
    };

    // Helper function to check if a permission matches a pattern with multi-layer wildcard support
    const matchesPattern = (
      permissionName: string,
      pattern: string,
    ): boolean => {
      if (pattern === "*") return true;
      if (pattern === permissionName) return true;

      // Handle multi-layer wildcards (e.g., "admin:api:*" matches "admin:api:users:read")
      if (pattern.endsWith(":*")) {
        const patternPrefix = pattern.slice(0, -2);
        // Check if permission starts with the pattern prefix
        if (permissionName === patternPrefix) return false; // "admin:api" doesn't match "admin:api:*"
        return permissionName.startsWith(`${patternPrefix}:`);
      }

      return false;
    };

    for (const role of roles) {
      // for each role candidate
      for (const rolePermission of role.permissions) {
        // for each permission in the role
        if (matchesPattern(permission, rolePermission.name)) {
          // [feature]: exclude permissions including wildcards
          if (rolePermission.exclude) {
            let isExcluded = false;
            for (const excludePattern of rolePermission.exclude) {
              if (matchesPattern(permission, excludePattern)) {
                isExcluded = true;
                break;
              }
            }
            if (isExcluded) {
              continue;
            }
          }

          result.isAuthorized = true; // OK !

          // but we also need to check if the user has ownership
          if (rolePermission.ownership) {
            // if ownership is true, we have to check all other matching permissions in case of ownership === false ...
            result.ownership = rolePermission.ownership;
          } else {
            // but if isAuthorized && ownership === false, we can break the loop \ :D /
            result.ownership = false;
            return result;
          }
        }
      }
    }

    return result;
  }

  /**
   * Creates a user account from the provided payload.
   */
  public async createUserFromToken(
    headerOrToken?: string,
    options: {
      permission?: Permission | string;
      realm?: string;
      verify?: JWTVerifyOptions;
    } = {},
  ): Promise<UserAccountToken> {
    const token = headerOrToken?.replace(/^Bearer\s+/i, "").trim();
    if (typeof token !== "string" || token === "") {
      throw new InvalidTokenError(
        "Invalid authorization header, maybe token is missing ?",
      );
    }

    const { result, keyName: realm } = await this.jwt.parse(
      token,
      options.realm,
      options.verify,
    );

    const info = this.createUserFromPayload(result.payload, realm);
    const realmRoles = this.getRoles(realm).filter((it) => it.default);
    const roles = info.roles ?? [];

    for (const role of realmRoles) {
      if (!roles.includes(role.name)) {
        roles.push(role.name);
      }
    }

    info.roles = roles;

    await this.alepha.events.emit("security:user:created", {
      realm,
      user: info,
    });

    let ownership: string | boolean | undefined;

    if (options.permission) {
      const check = this.checkPermission(options.permission, ...roles);
      if (!check.isAuthorized) {
        throw new SecurityError(
          `User is not allowed to access '${this.permissionToString(options.permission)}'`,
        );
      }

      ownership = check.ownership;
    }

    return {
      ...info,
      ownership,
      token,
      realm,
    };
  }

  /**
   * Checks if a user has a specific role.
   *
   * @param roleName - The role to check for.
   * @param permission - The permission to check for.
   * @returns True if the user has the role, false otherwise.
   */
  public can(roleName: string, permission: string | Permission): boolean {
    return this.checkPermission(permission, roleName).isAuthorized;
  }

  /**
   * Checks if a user has ownership of a specific permission.
   */
  public ownership(
    roleName: string,
    permission: string | Permission,
  ): string | boolean | undefined {
    return this.checkPermission(permission, roleName).ownership;
  }

  /**
   * Converts a permission object to a string.
   *
   * @param permission
   */
  public permissionToString(permission: Permission | string): string {
    if (typeof permission === "string") {
      return permission;
    }

    if (!permission.group) {
      return permission.name;
    }

    // Handle multi-layer groups (e.g., "admin:api" or "management:users")
    const groupParts = Array.isArray(permission.group)
      ? permission.group
      : [permission.group];

    return `${groupParts.join(":")}:${permission.name}`;
  }

  /**
   * Check that the user belongs to one of the required issuers.
   */
  public checkIssuers(user: UserAccountToken, issuers?: string[]) {
    if (issuers?.length && (!user.realm || !issuers.includes(user.realm))) {
      throw new ForbiddenError(
        `User must belong to issuer '${issuers.join("' or '")}' to access this route`,
      );
    }
  }

  /**
   * Store the resolved user in the atom for downstream access (useAuth, audit trail, etc.).
   */
  public storeUserInContext(user: UserAccountToken) {
    const decoded = this.alepha.codec.decode(userAccountInfoSchema, user);
    this.alepha.store.set(currentUserAtom, decoded);
  }

  // accessors

  public getRealms(): Realm[] {
    return this.realms;
  }

  /**
   * Resolve the realm an authenticated admin action is allowed to operate on.
   *
   * Admin endpoints accept a client-supplied target realm (e.g. a
   * `userRealmName` query param). Without scoping, a realm-A admin could act on
   * realm-B resources by simply naming realm B — a cross-tenant escalation
   * whenever realms are used as tenant boundaries. This binds the target realm
   * to the caller's own realm: omitting the target means "my realm"; naming a
   * *different* realm than the caller's is refused.
   *
   * Returns the effective realm to use (may be `undefined` in single-realm apps
   * where neither the caller nor the request carries a realm).
   */
  public assertRealmScope(
    user: { realm?: string } | undefined,
    requestedRealm: string | undefined,
  ): string | undefined {
    const callerRealm = user?.realm;
    if (requestedRealm && callerRealm && requestedRealm !== callerRealm) {
      throw new ForbiddenError(
        `Cross-realm access denied: authenticated in realm '${callerRealm}', requested realm '${requestedRealm}'.`,
      );
    }
    return requestedRealm ?? callerRealm;
  }

  /**
   * Retrieves the user account from the provided user ID.
   *
   * @param realm
   */
  public getRoles(realm?: string): Role[] {
    if (realm) {
      return [...(this.realms.find((it) => it.name === realm)?.roles ?? [])];
    }

    return this.realms.reduce<Role[]>((arr, it) => arr.concat(it.roles), []);
  }

  /**
   * Returns all permissions.
   *
   * @param user - Filter permissions by user.
   *
   * @return An array containing all permissions.
   */
  public getPermissions(user?: {
    roles?: Array<Role | string>;
    realm?: string;
  }): Permission[] {
    if (user?.roles) {
      const permissions: Permission[] = [];
      const roles = user.roles ?? [];

      for (const roleOrString of roles) {
        const role =
          typeof roleOrString === "string"
            ? this.getRoles(user.realm).find((it) => it.name === roleOrString)
            : roleOrString;

        if (!role) {
          throw new SecurityError(`Role '${roleOrString}' not found`);
        }

        if (role.permissions.some((it) => it.name === "*" && !it.exclude)) {
          return this.getPermissions();
        }

        for (const permission of role.permissions) {
          let ref: Permission[] = [];
          if (permission.name === "*") {
            ref.push(...this.permissions);
          } else if (permission.name.includes(":")) {
            // Handle multi-layer wildcards (e.g., "admin:api:*" or "users:read")
            const parts = permission.name.split(":");
            const lastPart = parts[parts.length - 1];

            if (lastPart === "*") {
              // Wildcard at any level (e.g., "admin:*", "admin:api:*")
              const groupPrefix = parts.slice(0, -1).join(":");

              ref.push(
                ...this.permissions.filter((it) => {
                  if (!it.group) return false;
                  // Match exact group or any sub-group
                  return (
                    it.group === groupPrefix ||
                    it.group.startsWith(`${groupPrefix}:`)
                  );
                }),
              );
            } else {
              // Specific permission (e.g., "users:read" or "admin:api:users:read")
              const name = lastPart;
              const groupParts = parts.slice(0, -1);
              const group = groupParts.join(":");

              ref.push(
                ...this.permissions.filter((it) => {
                  if (it.name !== name) return false;
                  if (!it.group) return false;
                  return it.group === group;
                }),
              );
            }
          } else {
            // all permissions without a group
            ref.push(
              ...this.permissions.filter(
                (it) => it.name === permission.name && !it.group,
              ),
            );
          }
          const exclude = permission.exclude;
          if (exclude) {
            // exclude permissions with multi-layer wildcard support
            ref = ref.filter((it) => {
              const permString = this.permissionToString(it);
              return !exclude.some((excludePattern) => {
                if (excludePattern === permString) return true;
                if (excludePattern.endsWith(":*")) {
                  const excludePrefix = excludePattern.slice(0, -2);
                  return permString.startsWith(`${excludePrefix}:`);
                }
                return false;
              });
            });
          }
          permissions.push(...ref);
        }
      }

      return [...new Set(permissions.filter((it) => it != null))];
    }

    return this.permissions;
  }

  /**
   * Retrieves the user ID from the provided payload object.
   *
   * @param payload - The payload object from which to extract the user ID.
   * @return The user ID as a string.
   */
  public getIdFromPayload(payload: Record<string, any>): string {
    if (payload.sub != null) {
      return String(payload.sub);
    }

    if (payload.id != null) {
      return String(payload.id);
    }

    if (payload.userId != null) {
      return String(payload.userId);
    }

    throw new SecurityError("Invalid JWT - missing id");
  }

  public getSessionIdFromPayload(
    payload: Record<string, any>,
  ): string | undefined {
    if (!payload) {
      return;
    }
    if (payload.sid) {
      return String(payload.sid);
    }
  }

  /**
   * Retrieves the roles from the provided payload object.
   * @param payload - The payload object from which to extract the roles.
   * @return An array of role strings.
   */
  public getRolesFromPayload(payload: Record<string, any>): string[] {
    return payload?.realm_access?.roles ?? payload?.roles ?? [];
  }

  public getPictureFromPayload(
    payload: Record<string, any>,
  ): string | undefined {
    if (!payload) {
      return;
    }

    if (payload.picture) {
      return payload.picture;
    }

    if (payload.avatar_url) {
      return payload.avatar_url;
    }

    if (payload.user_picture) {
      return payload.user_picture;
    }

    return undefined;
  }

  public getUsernameFromPayload(
    payload: Record<string, any>,
  ): string | undefined {
    if (!payload) {
      return;
    }

    if (payload.preferred_username) {
      return payload.preferred_username;
    }

    if (payload.username) {
      return payload.username;
    }

    return undefined;
  }

  public getEmailFromPayload(payload: Record<string, any>): string | undefined {
    if (!payload) {
      return;
    }

    if (payload.email) {
      return payload.email;
    }

    return undefined;
  }

  /**
   * Returns the name from the given payload.
   *
   * @param payload - The payload object.
   * @returns The name extracted from the payload, or an empty string if the payload is falsy or no name is found.
   */
  public getNameFromPayload(payload: Record<string, any>): string {
    if (!payload) {
      return this.UNKNOWN_USER_NAME;
    }

    if (payload.name) {
      return payload.name;
    }

    if (
      typeof payload.given_name === "string" &&
      typeof payload.family_name === "string"
    ) {
      return `${payload.given_name} ${payload.family_name}`.trim();
    }

    return this.UNKNOWN_USER_NAME;
  }

  public getOrganizationFromPayload(
    payload: Record<string, any>,
  ): string | undefined {
    if (!payload) {
      return;
    }

    if (typeof payload.organization === "string") {
      return payload.organization;
    }
  }

  /**
   * Extracts the tenant id from the JWT payload, when present.
   *
   * Tokens minted with no active tenant (single-tenant apps, server-to-server
   * calls before any request-scoped middleware runs) omit the claim, in which
   * case the resolver does not enforce a tenant match.
   */
  public getTenantFromPayload(
    payload: Record<string, any>,
  ): string | undefined {
    if (!payload) {
      return;
    }

    if (typeof payload.tenant === "string") {
      return payload.tenant;
    }
  }
}

// =====================================================================================================================

/**
 * A realm definition.
 */
export interface Realm {
  name: string;

  roles: Role[];

  /**
   * The secret key for the realm.
   *
   * Can be also a JWKS URL.
   */
  secret?: string | JSONWebKeySet | (() => string);

  /**
   * Asymmetric signing config. When set, this realm signs its tokens with an
   * asymmetric key and publishes the public keys via JWKS (see
   * `JwtProvider.getJwks`). Takes precedence over `secret`.
   */
  signing?: SigningConfig;

  /**
   * Create the user account info based on the raw JWT payload.
   * By default, SecurityProvider has his own implementation, but this method allow to override it.
   */
  profile?: (raw: Record<string, any>) => UserAccount;

  /**
   * Custom resolvers for this realm (sorted by priority).
   */
  resolvers?: IssuerResolver[];
}

export interface SecurityCheckResult {
  isAuthorized: boolean;
  ownership: string | boolean | undefined;
}
