import { Injectable } from '@nestjs/common';
import { ForbiddenException } from '@nestjs/common';
import { CaslAbilityFactory } from './casl-ability.factory';
import { AppAbility, AppSubject, AuthorizationRequest } from './interfaces';
import { CaslAction, permissionCodeSubject } from './casl.constants';

/**
 * Generic authorization service usable both inside and outside of HTTP
 * request context.
 *
 * ### HTTP use
 * Inject `AuthorizationService` into controllers/services and call
 * `abilityForRequest(req)` to get the current user's ability.
 *
 * ### Non-HTTP use (Agent Tool, background job, etc.)
 * Build an ability via `abilityFactory.createForUser(user)` and pass it
 * to `assert` / `can` directly.
 */
@Injectable()
export class AuthorizationService {
  constructor(private readonly abilityFactory: CaslAbilityFactory) {}

  /**
   * Build an {@link AppAbility} for the current HTTP request.
   *
   * @param req — Express/NestJS request with `req.user` populated by JWT.
   */
  abilityForRequest(req: AuthorizationRequest): AppAbility {
    if (!req.user) {
      throw new ForbiddenException('缺少可信授权身份');
    }
    return this.abilityFactory.createForUser(req.user);
  }

  /**
   * Assert that the given ability allows the action on the subject.
   * Throws `ForbiddenException` on denial.
   */
  assertAbility(ability: AppAbility, action: CaslAction, subject: AppSubject): void {
    if (!ability.can(action, subject)) {
      throw new ForbiddenException(
        `权限不足：${action} ${typeof subject === 'string' ? subject : 'resource'}`,
      );
    }
  }

  /**
   * Check whether the given ability allows the action on the subject.
   * Returns boolean — never throws.
   */
  canAbility(ability: AppAbility, action: CaslAction, subject: AppSubject): boolean {
    return ability.can(action, subject);
  }

  /**
   * Check whether the given ability allows the exact permission code.
   * Uses `CaslAction.Access` with `permissionCodeSubject(code)` — the same
   * mechanism as {@link PermissionGuard}.
   *
   * Returns boolean — never throws.
   */
  canPermission(ability: AppAbility, permissionCode: string): boolean {
    return ability.can(CaslAction.Access, permissionCodeSubject(permissionCode));
  }

  /**
   * Assert that the given ability allows the exact permission code.
   * Throws `ForbiddenException` on denial.
   */
  assertPermission(ability: AppAbility, permissionCode: string): void {
    if (!this.canPermission(ability, permissionCode)) {
      throw new ForbiddenException(
        `权限不足：缺少权限码 ${permissionCode}`,
      );
    }
  }

  /**
   * Assert that the user (from request) can perform an action.
   * Convenience shorthand — builds ability from request.
   */
  assert(req: AuthorizationRequest, action: CaslAction, subject: AppSubject): void {
    const ability = this.abilityForRequest(req);
    this.assertAbility(ability, action, subject);
  }

  /**
   * Check that the user (from request) can perform an action.
   * Convenience shorthand — builds ability from request.
   */
  can(req: AuthorizationRequest, action: CaslAction, subject: AppSubject): boolean {
    const ability = this.abilityForRequest(req);
    return this.canAbility(ability, action, subject);
  }
}
