import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import { IamAuditService } from './iam-audit.service';

/**
 * 异步任务 / 定时任务 / Webhook 的身份语义（规则 §5.3.13）
 *
 * 规则要求：非用户主体必须显式携带 actor 或 system principal；
 * 缺失必须抛 MissingActorException，**不得回退 Administrator**。
 *
 * 使用方式：
 * - 用户代理：`{ principal: 'user', userId: 'xxx' }`
 *   → 按用户身份加载权限 + DataScope
 * - 系统主体：`{ principal: 'system', source: 'cron:xxx' | 'queue:xxx' | 'webhook:provider' }`
 *   → 豁免 DataScope，但必须记审计日志
 */

export interface TaskActor {
  principal: 'user' | 'system';
  userId?: string;
  source?: string;
}

export class MissingActorException extends BadRequestException {
  constructor() {
    super({
      message: '异步任务必须显式携带 actor（userId 或 system principal）',
      code: 'MISSING_ACTOR',
    });
  }
}

/**
 * IamAuditLog.actor 为 UUID 列；非用户主体（cron/queue/webhook）统一用此哨兵 UUID 作 actor，
 * 真正的来源（cron name / queue name / webhook provider）放在 after.source 字段。
 */
export const SYSTEM_PRINCIPAL_ACTOR_UUID = '00000000-0000-0000-0000-000000000000';

@Injectable()
export class SystemPrincipalService {
  private readonly logger = new Logger(SystemPrincipalService.name);

  constructor(private readonly audit: IamAuditService) {}

  /**
   * 任务入口校验：无 actor 抛异常；system actor 记审计
   */
  async validateAndAuditActor(actor: TaskActor | undefined): Promise<void> {
    if (!actor || (!actor.userId && !actor.source)) {
      throw new MissingActorException();
    }
    if (actor.principal === 'system') {
      if (!actor.source) {
        throw new BadRequestException('System principal 必须提供 source');
      }
      await this.audit.record({
        actor: SYSTEM_PRINCIPAL_ACTOR_UUID,
        action: 'ADMIN_BYPASS',
        resource: 'SystemTask',
        after: { source: actor.source },
      });
      this.logger.log(`system principal task invoked source=${actor.source}`);
    }
  }

  /**
   * 便捷工厂：给同步代码用的 system principal
   */
  system(source: string): TaskActor {
    if (!source?.trim()) throw new Error('source 必填');
    return { principal: 'system', source };
  }
}
