// https://iwiki.woa.com/pages/viewpage.action?pageId=4007728669
// node version >= v12.22
// npm i jose@4.14.4
import { UserType } from '@gadmin2n/nest-common';
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  SetMetadata,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ApiSecurity } from '@nestjs/swagger';
import { DECORATORS_PREFIX } from '@nestjs/swagger/dist/constants';
import * as path from 'path';
import { UserService } from '../modules/user/user.service';
import { loggerInstance } from './logger';
import TaiHuId, { GAdminTokenName } from './taihu';
import { getIdentity as getWoaIdentity } from './woaAuth';

const logger = loggerInstance.child({
  file: path.basename(__filename),
});

const API_SECURITY = `${DECORATORS_PREFIX}/apiSecurity`;
export function ApiKeyAuth(name = 'X-API-KEY') {
  return ApiSecurity(name);
}

export const AllowUnauthorizedRequest = () =>
  SetMetadata('allowUnauthorizedRequest', true);

export const AllowApiKeyRequest = () => SetMetadata('allowApiKeyRequest', true);

async function getIdentity(headers, cookies): Promise<UserType> {
  // 先验证woa登录态 https://iwiki.woa.com/p/4007728669#nodejs-%E7%A4%BA%E4%BE%8B
  try {
    const { staffid, staffname } = await getWoaIdentity(
      headers,
      process.env.TAIHU_APP_TOKEN || '',
    );
    if (staffid) {
      logger.info(
        `woa_auth_success - 用户ID: ${staffid}, 用户名: ${staffname}`,
      );
      return { userid: staffid, username: staffname };
    }
  } catch (e) {
    logger.warn(`woa_auth_failed - WOA认证失败: ${e.message}`);
  }

  // 再验证taihu登录态 https://iwiki.woa.com/p/4008986665
  const token = headers[GAdminTokenName] || cookies[GAdminTokenName];
  const userInfo = await TaiHuId.verify(token);

  logger.info(
    `taihu_auth_success - Taihu认证成功 - 用户ID: ${userInfo.sub}, Token长度: ${
      token ? token.length : 0
    }`,
  );

  return { userid: userInfo.sub, username: userInfo.sub };
}

@Injectable()
export default class AuthGuard implements CanActivate {
  constructor(private reflector: Reflector, private userService: UserService) {}

  private async getApikeyIdentity(req): Promise<UserType> {
    const apiKey = req.headers['x-api-key'] ?? req.query.api_key;

    if (apiKey) {
      const record = await this.userService.findByApiKey(apiKey);
      if (record && record.isActive && record.isApiKey) {
        logger.info(
          `apikey_auth_success - API Key认证成功 - 用户ID: ${
            record.userid
          }, Key前缀: ${apiKey.substring(0, 8)}...`,
        );
        return {
          userid: record.userid,
          username: record.username || record.userid,
        };
      }
    }

    logger.warn(
      `apikey_auth_failed - API Key认证失败 - 提供的Key: ${
        apiKey ? `${apiKey.substring(0, 8)}...` : 'none'
      }`,
    );

    throw new UnauthorizedException(`error apikey: ${apiKey}`);
  }

  canActivate(context: ExecutionContext): boolean | Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const handler = context.getHandler();
    const controller = context.getClass();

    const allowUnauthorizedRequest = this.reflector.get<boolean>(
      'allowUnauthorizedRequest',
      handler,
    );

    if (allowUnauthorizedRequest) {
      logger.info(
        `auth_bypassed - 认证被绕过 - 端点: ${request.method} ${request.url}, 控制器: ${controller.name}`,
      );
      return true;
    }

    const allowApiKeyRequest =
      this.reflector.get<boolean>(API_SECURITY, handler) ||
      this.reflector.get<boolean>('allowApiKeyRequest', handler);

    logger.info(
      `auth_attempt - 认证尝试 - 端点: ${request.method} ${
        request.url
      }, 控制器: ${controller.name}, 允许API Key: ${allowApiKeyRequest}, IP: ${
        request.ip || request.connection.remoteAddress
      }`,
    );

    return (
      allowApiKeyRequest
        ? this.getApikeyIdentity(request)
        : Promise.reject("don't allow api key request.")
    )
      .catch((error) => {
        logger.warn(
          `apikey_fallback - API Key认证失败，回退到WOA/Taihu认证 - 端点: ${request.method} ${request.url}, 错误: ${error.message}`,
        );

        return getIdentity(request.headers, request.cookies);
      })
      .then((userInfo) => {
        request.user = userInfo;

        logger.info(
          `auth_success - 认证成功 - 用户ID: ${userInfo.userid}, 用户名: ${userInfo.username}, 端点: ${request.method} ${request.url}`,
        );

        return true;
      })
      .catch((error) => {
        logger.error(
          `auth_failed - 认证失败 - 端点: ${request.method} ${request.url}, 错误: ${error.message}`,
        );

        throw new UnauthorizedException(error);
      });
  }
}
