import {
  Injectable,
  Logger,
  ForbiddenException,
  NotImplementedException,
} from '@nestjs/common';
import { DataScopeType } from '@prisma/client';
import { OrganizationContextService } from './organization-context.service';
import { isAdministrator, hasPermissionCode } from '../utils/role-check.util';

/**
 * DataScope 解析 + WHERE 生成 + 单记录归属校验。
 *
 * 规则对齐（docs/standards/09-iam-security.md）：
 * - §5.3.3 多角色按 resource 独立合并，`*` 兜底与精确匹配共同候选池，取最宽
 * - §5.3.4 ORGANIZATION scope 的 WHERE = (organizationId IN orgIds) OR (organizationId IS NULL)
 * - §5.3.4 DEPARTMENT 取 active 部门并集；DEPARTMENT_TREE 取并集 + 子树
 * - §5.3.7 assertAccess 写路径手动调用
 * - §5.3.16 部门树超限抛异常（OrgContextService 负责）
 * - §5.3.17 未知 resource WARN 日志 + metric（不静默降级）
 * - §8 禁止 #9 CUSTOM 抛 NotImplementedException
 */

export interface DataScopeFieldMapping {
  userId?: string;
  departmentId?: string;
  organizationId?: string;
  regionId?: string;
}

export interface UserDataScope {
  resource: string;
  scopeType: DataScopeType | string;
}

export interface DataScopeUser {
  userId: string;
  currentRegion?: string;
  currentOrganizationId?: string;
  dataScopes?: UserDataScope[];
  roles?: string[];
  permissions?: string[];
}

const DEFAULT_FIELD_MAPPING: Required<DataScopeFieldMapping> = {
  userId: 'createdById',
  departmentId: 'departmentId',
  organizationId: 'organizationId',
  regionId: 'regionId',
};

const SCOPE_PRIORITY: Record<string, number> = {
  [DataScopeType.SELF]: 1,
  [DataScopeType.DEPARTMENT]: 2,
  [DataScopeType.DEPARTMENT_TREE]: 3,
  [DataScopeType.ORGANIZATION]: 4,
  [DataScopeType.REGION]: 5,
  [DataScopeType.ALL]: 6,
  [DataScopeType.CUSTOM]: 3,
};

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

  constructor(private orgContextService: OrganizationContextService) {}

  /**
   * 解析当前用户对该 resource 的 scope → 生成 Prisma WHERE 过滤。
   * 用于读路径（Interceptor 自动注入）。
   */
  async resolve(
    user: DataScopeUser,
    resource: string,
    fieldMapping?: DataScopeFieldMapping,
  ): Promise<Record<string, any>> {
    if (this.isAdmin(user)) return {};

    const scopeType = this.resolveEffectiveScope(
      user.dataScopes || [],
      resource,
    );
    return this.buildFilter(scopeType, user, fieldMapping);
  }

  /**
   * 写路径归属校验（规则 §5.3.7）。
   * 不通过抛 ForbiddenException（HTTP 403）。
   */
  async assertAccess(
    user: DataScopeUser,
    resource: string,
    record: Record<string, any>,
    fieldMapping?: DataScopeFieldMapping,
  ): Promise<void> {
    if (this.isAdmin(user)) return;

    if (!record) {
      // 空记录视为不在范围内
      throw new ForbiddenException('No access to this record');
    }

    const scopeType = this.resolveEffectiveScope(
      user.dataScopes || [],
      resource,
    );

    // 对全局记录（organizationId=null）的写操作严格拦截，需 system:admin
    const f = { ...DEFAULT_FIELD_MAPPING, ...fieldMapping };
    if (record[f.organizationId] == null) {
      if (!this.hasPermission(user, 'system:admin')) {
        throw new ForbiddenException({
          message: '系统记录只读，需要 system:admin 权限',
          code: 'SYSTEM_RECORD_READ_ONLY',
        });
      }
    }

    const inScope = await this.isRecordInScope(
      scopeType,
      user,
      record,
      fieldMapping,
    );
    if (!inScope) {
      throw new ForbiddenException('No access to this record');
    }
  }

  /**
   * 对 resource 做多角色合并（按 resource 独立 + `*` 兜底并入候选池）
   * 规则 §5.3.3。
   */
  resolveEffectiveScope(
    dataScopes: UserDataScope[],
    resource: string,
  ): DataScopeType {
    const candidates = dataScopes.filter(
      (s) => s.resource === resource || s.resource === '*',
    );

    if (candidates.length === 0) {
      // 未知 resource / 未配置 → WARN + metric，兜底 SELF（规则 §5.3.17 + §0.3 假设 #8）
      this.logger.warn(
        `DataScope 未配置或 resource 拼写错误 resource="${resource}"，兜底使用 SELF`,
      );
      this.incrementMetric('data_scope.unknown_resource', { resource });
      return DataScopeType.SELF;
    }

    const widest = candidates.reduce<DataScopeType>((acc, s) => {
      const cur = s.scopeType as DataScopeType;
      return (SCOPE_PRIORITY[cur] ?? 0) > (SCOPE_PRIORITY[acc] ?? 0) ? cur : acc;
    }, DataScopeType.SELF);

    return widest;
  }

  private async isRecordInScope(
    scopeType: DataScopeType,
    user: DataScopeUser,
    record: Record<string, any>,
    fields?: DataScopeFieldMapping,
  ): Promise<boolean> {
    const f = { ...DEFAULT_FIELD_MAPPING, ...fields };

    switch (scopeType) {
      case DataScopeType.SELF:
        return record[f.userId] === user.userId;

      case DataScopeType.DEPARTMENT: {
        const deptIds = await this.orgContextService.getUserDepartmentIds(
          user.userId,
        );
        return deptIds.includes(record[f.departmentId]);
      }

      case DataScopeType.DEPARTMENT_TREE: {
        const treeIds = await this.orgContextService.getDepartmentTreeIds(
          user.userId,
        );
        return treeIds.includes(record[f.departmentId]);
      }

      case DataScopeType.ORGANIZATION: {
        const orgIds = await this.orgContextService.getUserOrganizationIds(
          user.userId,
        );
        return orgIds.includes(record[f.organizationId]);
      }

      case DataScopeType.REGION:
        return record[f.regionId] === user.currentRegion;

      case DataScopeType.ALL:
        return true;

      case DataScopeType.CUSTOM:
        throw new NotImplementedException(
          'CUSTOM DataScope 被规则永久禁用（§5.3.1 职责边界）',
        );

      default:
        return record[f.userId] === user.userId;
    }
  }

  private async buildFilter(
    scopeType: DataScopeType,
    user: DataScopeUser,
    fields?: DataScopeFieldMapping,
  ): Promise<Record<string, any>> {
    const f = { ...DEFAULT_FIELD_MAPPING, ...fields };

    switch (scopeType) {
      case DataScopeType.SELF:
        return { [f.userId]: user.userId };

      case DataScopeType.DEPARTMENT: {
        const deptIds = await this.orgContextService.getUserDepartmentIds(
          user.userId,
        );
        return { [f.departmentId]: { in: deptIds } };
      }

      case DataScopeType.DEPARTMENT_TREE: {
        const treeIds = await this.orgContextService.getDepartmentTreeIds(
          user.userId,
        );
        return { [f.departmentId]: { in: treeIds } };
      }

      case DataScopeType.ORGANIZATION: {
        const orgIds = await this.orgContextService.getUserOrganizationIds(
          user.userId,
        );
        // 规则 §5.3.4：全局记录（organizationId=null）对所有组织可见
        return {
          OR: [
            { [f.organizationId]: { in: orgIds } },
            { [f.organizationId]: null },
          ],
        };
      }

      case DataScopeType.REGION:
        if (!user.currentRegion) {
          // 显式防护：currentRegion 为空 → 部署/上下文配置错误，
          // 不能让 WHERE regionId = undefined 静默退化为"查不到"。
          throw new ForbiddenException({
            message: 'REGION scope 要求请求上下文携带 currentRegion',
            code: 'REGION_CONTEXT_MISSING',
          });
        }
        return { [f.regionId]: user.currentRegion };

      case DataScopeType.ALL:
        return {};

      case DataScopeType.CUSTOM:
        throw new NotImplementedException(
          'CUSTOM DataScope 被规则永久禁用（§5.3.1 职责边界）',
        );

      default:
        return { [f.userId]: user.userId };
    }
  }

  private isAdmin(user: DataScopeUser): boolean {
    return isAdministrator(user);
  }

  private hasPermission(user: DataScopeUser, perm: string): boolean {
    return this.isAdmin(user) || hasPermissionCode(user.permissions, perm);
  }

  /**
   * Metric 接入点 — 当前走日志，后续可接 Prometheus / 其他监控栈
   */
  private incrementMetric(name: string, tags: Record<string, string>): void {
    this.logger.debug(`metric:${name} ${JSON.stringify(tags)}`);
  }
}
