import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClsService } from 'nestjs-cls';
import { UserEntity } from '../../entities/user.entity';
import {
  RolePermissionEntity,
  ModuleName,
  AccessLevel,
} from '../../entities/role-permission.entity';
import { CLS_USER_ID, CLS_TENANT_ID } from '../cls/cls.constants';
import { RedisService } from '../../redis/redis.service';

const ACCESS_HIERARCHY: Record<AccessLevel, number> = {
  [AccessLevel.NONE]: 0,
  [AccessLevel.VIEW]: 1,
  [AccessLevel.CREATE]: 2,
  [AccessLevel.EDIT]: 3,
  [AccessLevel.DELETE]: 4,
};

/**
 * PermissionCheckService — lets service-layer code (not guards) ask
 * "does the current user have at least X access on module Y?".
 *
 * Used for FIELD-LEVEL gating (e.g. stripping `selling_total` from
 * a quote response for sales users). Endpoint-level gating keeps
 * using `@Permissions(...)` + `PermissionGuard` as before.
 *
 * Cache key matches the guard's format (`perm:role:{tenantId}:{roleId}`)
 * so both share the same Redis cache and invalidation.
 */
@Injectable()
export class PermissionCheckService {
  private static readonly CACHE_TTL = 300;
  private static readonly CACHE_PREFIX = 'perm:role:';

  constructor(
    @InjectRepository(UserEntity)
    private readonly userRepo: Repository<UserEntity>,
    @InjectRepository(RolePermissionEntity)
    private readonly rolePermRepo: Repository<RolePermissionEntity>,
    private readonly cls: ClsService,
    private readonly redis: RedisService,
  ) {}

  /** Resolve the current user's role_id from CLS user_id. null if
   *  no user context (e.g. lookup endpoint hit without a role). */
  private async getCurrentRoleId(): Promise<string | null> {
    const userId = this.cls.get<string>(CLS_USER_ID);
    if (!userId) return null;
    const user = await this.userRepo.findOne({
      where: { id: userId },
      select: {
        id: true,
        role_id: true,
      },
    });
    return user?.role_id || null;
  }

  /** Returns the access level the current user has on `module`.
   *  NONE when no context / no row found. */
  async getAccessLevel(module: ModuleName): Promise<AccessLevel> {
    const tenantId = this.cls.get<string>(CLS_TENANT_ID);
    const roleId = await this.getCurrentRoleId();
    if (!tenantId || !roleId) return AccessLevel.NONE;

    const cacheKey = `${PermissionCheckService.CACHE_PREFIX}${tenantId}:${roleId}`;
    let perms: Array<{ module: string; access_level: AccessLevel }>;
    const cached = await this.redis.get(cacheKey);
    if (cached) {
      perms = JSON.parse(cached);
    } else {
      const rows = await this.rolePermRepo.find({
        where: { role: { id: roleId }, tenant_id: tenantId, is_deleted: false },
        select: {
          module: true,
          access_level: true,
        },
      });
      perms = rows.map((p) => ({ module: p.module, access_level: p.access_level }));
      await this.redis.set(cacheKey, JSON.stringify(perms), PermissionCheckService.CACHE_TTL);
    }
    const hit = perms.find((p) => p.module === module);
    return hit?.access_level ?? AccessLevel.NONE;
  }

  /** True when the current user has at least `minLevel` on `module`. */
  async hasAtLeast(module: ModuleName, minLevel: AccessLevel): Promise<boolean> {
    const current = await this.getAccessLevel(module);
    return ACCESS_HIERARCHY[current] >= ACCESS_HIERARCHY[minLevel];
  }
}
