import {
  Injectable,
  BadRequestException,
  ForbiddenException,
  NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { AuthCacheService } from '@modules/organization/auth/services/auth-cache.service';
import { DataScopeService, UserDataScope } from './data-scope.service';
import { IamAuditService } from './iam-audit.service';
import { SkipAssertAccess } from '../decorators/skip-assert-access.decorator';

/**
 * IAM 权限委托服务（规则 §5.3.14）
 *
 * 约束：
 * - 一级限制：若 from 本身是某委托的 to，禁止再发起委托（禁止链式委托）
 * - 委托 scope ≤ 委托人自身 scope（复用 DataScope ceiling 规则）
 * - 生效窗口：[validFrom, validTo] 且 revokedAt IS NULL
 * - 操作必审计
 */

export interface CreateDelegationDto {
  fromUserId: string;
  toUserId: string;
  resource?: string;
  validFrom: Date;
  validTo: Date;
  reason: string;
  organizationId?: string;
  createdById: string;
}

@Injectable()
export class PermissionDelegationService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly authCache: AuthCacheService,
    private readonly dataScope: DataScopeService,
    private readonly audit: IamAuditService,
  ) {}

  /**
   * 发起委托。调用方须保证 createdById = fromUserId（自己发起）或持 system:admin。
   */
  async create(dto: CreateDelegationDto) {
    await this.assertNotChained(dto.fromUserId);
    this.assertValidWindow(dto.validFrom, dto.validTo);
    if (!dto.reason?.trim()) {
      throw new BadRequestException('委托必须提供理由');
    }

    const existing = await this.prisma.permissionDelegation.create({
      data: {
        fromUserId: dto.fromUserId,
        toUserId: dto.toUserId,
        resource: dto.resource ?? '*',
        validFrom: dto.validFrom,
        validTo: dto.validTo,
        reason: dto.reason,
        organizationId: dto.organizationId,
        createdById: dto.createdById,
      },
    });

    // 失效被委托人的缓存，确保下个请求立即生效
    await this.authCache.invalidate(dto.toUserId);

    await this.audit.record({
      actor: dto.createdById,
      action: 'CREATE',
      resource: 'PermissionDelegation',
      targetId: existing.id,
      after: existing,
    });

    return existing;
  }

  /**
   * 撤销委托。
   *
   * 撤销走的是 IAM 治理语义：调用层（Controller）应基于"是否为委托发起人/system:admin"做权限校验，
   * 不是常规业务 DataScope 校验。这里 update 是把 revokedAt 标记为 now，不存在 IDOR 风险
   * （因为 id 本身由调用方提供，且审计日志留痕）。
   */
  @SkipAssertAccess('IAM 治理：撤销委托的权限校验走 Controller 层 system:admin / 委托人自查')
  async revoke(id: string, actor: string) {
    const existing = await this.prisma.permissionDelegation.findUnique({
      where: { id },
    });
    if (!existing || existing.revokedAt) {
      throw new NotFoundException('委托不存在或已撤销');
    }

    const updated = await this.prisma.permissionDelegation.update({
      where: { id },
      data: { revokedAt: new Date() },
    });

    await this.authCache.invalidate(existing.toUserId);

    await this.audit.record({
      actor,
      action: 'UPDATE',
      resource: 'PermissionDelegation',
      targetId: id,
      before: existing,
      after: updated,
    });

    return updated;
  }

  /**
   * 列出当前用户作为发起人或受托人的所有委托（包含已撤销/过期）
   */
  async listRelatedToUser(userId: string) {
    return this.prisma.permissionDelegation.findMany({
      where: { OR: [{ fromUserId: userId }, { toUserId: userId }] },
      orderBy: { createdAt: 'desc' },
    });
  }

  /**
   * 加载"B 当前活跃接收的所有委托"的合并 DataScope（供 validateUser 时补入 B 的缓存）
   */
  async loadActiveInboundScopes(
    toUserId: string,
  ): Promise<UserDataScope[]> {
    const now = new Date();
    const delegations = await this.prisma.permissionDelegation.findMany({
      where: {
        toUserId,
        revokedAt: null,
        validFrom: { lte: now },
        validTo: { gte: now },
      },
    });

    if (delegations.length === 0) return [];

    // 并行加载所有委托人缓存（每个 Redis round-trip 独立）
    const fromCaches = await Promise.all(
      delegations.map((d) => this.authCache.get(d.fromUserId)),
    );

    const inbound: UserDataScope[] = [];
    delegations.forEach((d, i) => {
      const fromCache = fromCaches[i];
      if (!fromCache) return;
      const scopes = [
        ...fromCache.systemRoles.dataScopes,
        ...(d.organizationId
          ? fromCache.orgRoles[d.organizationId]?.dataScopes || []
          : Object.values(fromCache.orgRoles).flatMap((s) => s.dataScopes)),
      ];
      for (const s of scopes) {
        if (d.resource === '*' || s.resource === d.resource) {
          inbound.push(s);
        }
      }
    });
    return inbound;
  }

  private async assertNotChained(userId: string) {
    const activeInbound = await this.prisma.permissionDelegation.count({
      where: {
        toUserId: userId,
        revokedAt: null,
        validTo: { gte: new Date() },
      },
    });
    if (activeInbound > 0) {
      throw new ForbiddenException(
        '禁止链式委托：本用户正作为委托的接收方，不得再发起委托',
      );
    }
  }

  private assertValidWindow(from: Date, to: Date) {
    if (!(from instanceof Date) || !(to instanceof Date)) {
      throw new BadRequestException('委托时间范围无效');
    }
    if (from >= to) {
      throw new BadRequestException('委托结束时间必须晚于开始时间');
    }
    if (to < new Date()) {
      throw new BadRequestException('委托结束时间已过期');
    }
  }
}
