// auth.middleware.ts

import Auth from '@/entities/core/auth';
import { Constant } from '@/ultils/constant';

import {
  ForbiddenException,
  HttpStatus,
  NestMiddleware,
  UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { NextFunction, Request, Response } from 'express';
import { AuthorService } from '../services/author.service';
import AppResponse from '@/system/response/ktq-response';
import { SystemLang } from '@/system/lang/system.lang';
import * as _ from 'lodash';
import { Permission } from '@/entities/permission.entity';

export class AuthorMiddleware<T extends Auth> implements NestMiddleware {
  constructor(
    protected readonly jwtService: JwtService,
    protected readonly service: AuthorService<T>,
  ) {}

  throwForbidden() {
    throw new ForbiddenException(
      AppResponse.toResponse(null, {
        message: SystemLang.getText('messages', 'forbidden'),
        status_code: HttpStatus.FORBIDDEN,
      }),
    );
  }

  hasPermission(permissions: Permission[], expected: string) {
    return _.some(permissions, ['permission_code', expected]);
  }

  async use(req: Request, res: Response, next: NextFunction) {
    // Dữ liệu của user hiện tại
    const user = req[Constant.ATTR_REQ_DATA];

    // Method của resource hiện tại đang gọi đến server
    const httpMethod = req.method; // "GET", "POST", ...

    // Path của resource hiện tại đang gọi đến server
    const path = req.path; // "/admin/users", v.v.

    // Lấy resource trong DB với query
    const resource = await this.service.currentResource({
      method: httpMethod,
      path: path.replaceAll(process.env.PREFIX_VERSION, ''),
    });

    // Nếu không có resource -> tức là resource này không được config -> không cần author
    // Nếu is public của resource bằng true => tức là resource này không cần author
    if (!resource || resource.is_public) return next();

    // Nếu disable -> tức là chức năng resource này đang bị tắt -> throw lỗi
    if (resource.disabled) {
      return next(
        new ForbiddenException(
          AppResponse.toResponse(null, {
            message: SystemLang.getText('messages', 'feature_disabled'), // ví dụ: "Tính năng này đang tạm ngưng"
            status_code: HttpStatus.FORBIDDEN,
            bonus: {
              error_code: 'FEATURE_DISABLED',
            },
          }),
        ),
      );
    }

    // Lấy các role của user hiện tại
    const roles = await this.service.roles(user);

    // Nếu có root role thì không cần thực hiện các bước tiếp theo nữa -> ROOT là quyền tối cao
    if (await this.service.isRootRole(roles)) {
      return next();
    }

    // Xét duyệt 2 tầng
    // 1. Xét tầng resource
    // + Kiểm tra quyền của user ở tầng resource (resource_permissions)
    // 2. Xét tầng role
    // + Kiểm tra permission của role xem có quyền truy cập vào resource này không (role_permissions)
    // => Nếu cả 2 tầng không hợp lệ thì tức là user không có quyền thực thi trên resource này.

    // Tầng 1. Kiểm tra resource
    // Lấy các perission của resource mà user này có thể tác động
    const resourcePermissions = await this.service.getPermissionsForResource(
      user,
      resource,
    );

    // Xử lí:
    // Nếu resourcePermissions bằng [] thì tức là chưa phần quyền gì cho resource -> pass sang tầng 2
    // Nếu có permission nào của user khớp với resource này -> tức là user này có quyền tác động đến resource này -> pass qua
    if (!_.isEmpty(resourcePermissions)) {
      // Đã được phân quyền cụ thể → Chỉ được phép theo đúng quyền đã phân
      const hasDirectPermission = this.hasPermission(
        resourcePermissions,
        resource.function_permisstion,
      );

      if (hasDirectPermission) {
        return next();
      }

      // Nếu không có quyền truy cập thích hợp -> throw lỗi
      // VD: Đỗi với resource này USER chỉ có quyền [Create, Read]
      // + Nhưng resource này cần quyền Update
      // + Cho dù role có chứa quyền Update đi chăng nữa -> cũng được tính là không có quyền
      return next(this.throwForbidden());
    }

    // Tầng 2. Kiểm tra role
    // Kiểm tra xem resource này có nằm trong danh sách cho phép của roles hiện tại không
    const isValidresrouce = await this.service.isValidResource(resource, roles);

    // Nếu không nằm trong danh sach -> không được phép truy cập -> throw lỗi
    if (!isValidresrouce) return next(this.throwForbidden());

    // Lấy hết các quyền mà role này đang sỡ hữu
    const rolePermissions = await this.service.rolePermissions(roles);

    // Nếu rolePermissions = [] tức là trong list role của user này không có bất kì quyền nào -> throw lỗi
    if (_.isEmpty(rolePermissions)) {
      return next(this.throwForbidden());
    }

    // Kiểm tra trong list permission có quyền nào phù hợp với resource này không
    const hasDirectPermission = this.hasPermission(
      rolePermissions,
      resource.function_permisstion,
    );

    // Nếu không có -> không có quyền tác động -> throw lỗi
    if (!hasDirectPermission) {
      return next(this.throwForbidden());
    }

    next();
  }
}
