import {
  Injectable,
  CanActivate,
  ExecutionContext,
  ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ClsService } from 'nestjs-cls';
import { RedisService } from '../../redis/redis.service';
import { RolePermissionEntity, AccessLevel } from '../../entities/role-permission.entity';
import { PERMISSION_KEY } from '../decorators/permissions.decorator';
import { CLS_TENANT_ID } from '../../common/cls/cls.constants';

export interface PermissionRequirement {
  module: string;
  accessLevel: AccessLevel;
}

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

/**
 * PermissionGuard — dynamic, Redis-cached permission checks.
 *
 * Per CLAUDE.md:
 * - Never hardcode role checks — always use dynamic permission guard
 * - All permission checks via DB/Redis only
 * - Permissions cached in Redis → invalidate on any permission change
 * - Reads tenant_id from CLS (not from request)
 */
@Injectable()
export class PermissionGuard implements CanActivate {
  private static readonly CACHE_TTL = 300;
  private static readonly CACHE_PREFIX = 'perm:role:';

  constructor(
    private readonly reflector: Reflector,
    private readonly redisService: RedisService,
    @InjectRepository(RolePermissionEntity)
    private readonly rolePermRepo: Repository<RolePermissionEntity>,
    private readonly cls: ClsService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const requirement = this.reflector.getAllAndOverride<PermissionRequirement>(
      PERMISSION_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!requirement) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (!user || !user.role) {
      throw new ForbiddenException('No role assigned');
    }

    // Read tenant_id from CLS — not from request
    const tenantId = this.cls.get<string>(CLS_TENANT_ID);

    const rolePermissions = await this.getRolePermissions(user.role.id, tenantId);
    const modulePermission = rolePermissions.find(
      (p) => p.module === requirement.module,
    );

    if (modulePermission) {
      const userLevel = ACCESS_HIERARCHY[modulePermission.access_level] || 0;
      const requiredLevel = ACCESS_HIERARCHY[requirement.accessLevel] || 0;

      if (userLevel >= requiredLevel) {
        return true;
      }
    }

    throw new ForbiddenException(
      `Insufficient permissions for ${requirement.module}:${requirement.accessLevel}`,
    );
  }

  private async getRolePermissions(
    roleId: string,
    tenantId: string,
  ): Promise<Array<{ module: string; access_level: AccessLevel }>> {
    const cacheKey = `${PermissionGuard.CACHE_PREFIX}${tenantId}:${roleId}`;

    const cached = await this.redisService.get(cacheKey);
    if (cached) {
      return JSON.parse(cached);
    }

    const permissions = await this.rolePermRepo.find({
      where: {
        role: { id: roleId },
        tenant_id: tenantId,
        is_deleted: false,
      },
      select: ['module', 'access_level'],
    });

    const result = permissions.map((p) => ({
      module: p.module,
      access_level: p.access_level,
    }));

    await this.redisService.set(
      cacheKey,
      JSON.stringify(result),
      PermissionGuard.CACHE_TTL,
    );

    return result;
  }
}
