/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#define USING_LOG_PREFIX SQL_RESV
#include "sql/resolver/ddl/ob_create_profile_resolver.h"
#include "sql/resolver/ddl/ob_create_profile_stmt.h"
#include "sql/resolver/ddl/ob_ddl_resolver.h"
#include "share/ob_rpc_struct.h"
#include "share/schema/ob_schema_struct.h"
#include "share/schema/ob_schema_service.h"

namespace oceanbase {
using namespace common;
using namespace share::schema;

namespace sql {

ObUserProfileResolver::ObUserProfileResolver(ObResolverParams& params) : ObDDLResolver(params)
{}

ObUserProfileResolver::~ObUserProfileResolver()
{}

int ObUserProfileResolver::fill_arg(int64_t type, ObObj& value, obrpc::ObProfileDDLArg& arg)
{
  int ret = OB_SUCCESS;
  int64_t schema_value = 0;
  double d_value = 0;
  float f_value = 0;
  number::ObNumber num_value;
  number::ObNumber timestamp_value;
  number::ObNumber usec_per_day;

  if (OB_ISNULL(params_.allocator_)) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("invalid allocator", K(ret));
  } else {
    switch (type) {
      case ObProfileSchema::FAILED_LOGIN_ATTEMPTS:
        if (OB_FAIL(value.get_number(num_value))) {
          LOG_WARN("fail to get numer", K(ret));
        } else if (!num_value.is_valid_int64(schema_value)) {
          ret = OB_INVALID_NUMERIC;
          LOG_WARN("fail to get int64_t", K(ret));
        }
        break;
      case ObProfileSchema::PASSWORD_LOCK_TIME:
      case ObProfileSchema::PASSWORD_LIFE_TIME:
      case ObProfileSchema::PASSWORD_GRACE_TIME:
        switch (value.get_type()) {
          case ObNumberType: {
            if (OB_FAIL(value.get_number(num_value))) {
              LOG_WARN("fail to get numer", K(ret));
            } else if (OB_FAIL(usec_per_day.from(USECS_PER_DAY, *params_.allocator_))) {
              LOG_WARN("fail to from interger", K(ret));
            } else if (OB_FAIL(num_value.mul(usec_per_day, timestamp_value, *params_.allocator_))) {
              LOG_WARN("fail to do mul", K(ret));
            } else if (OB_FAIL(timestamp_value.round(0))) {
              LOG_WARN("fail to do round", K(ret));
            } else if (!timestamp_value.is_valid_int64(schema_value)) {
              ret = OB_INVALID_NUMERIC;
              LOG_WARN("fail to get int64_t", K(ret));
            }
            break;
          }
          case ObDoubleType: {
            if (OB_FAIL(value.get_double(d_value))) {
              LOG_WARN("fail to get double", K(ret));
            } else {
              schema_value = static_cast<int64_t>(d_value * USECS_PER_DAY);
            }
            break;
          }
          case ObFloatType: {
            if (OB_FAIL(value.get_float(f_value))) {
              LOG_WARN("fail to get float", K(ret));
            } else {
              schema_value = static_cast<int64_t>(f_value * USECS_PER_DAY);
            }
            break;
          }
          default:
            ret = OB_NOT_SUPPORTED;
            LOG_WARN("not support value type", K(value.get_type()));
        }
        break;
      default:
        ret = OB_NOT_SUPPORTED;
        LOG_WARN("not support profile type", K(type));
    }
    if (OB_SUCC(ret) && schema_value <= 0) {
      ret = OB_ERR_INVALID_RESOURCE_LIMIT;
      LOG_USER_ERROR(OB_ERR_INVALID_RESOURCE_LIMIT, ObProfileSchema::PARAM_VALUE_NAMES[type]);
    }
  }

  if (OB_SUCC(ret)) {
    if (GET_MIN_CLUSTER_VERSION() < CLUSTER_VERSION_2276 &&
        (type == ObProfileSchema::PASSWORD_LIFE_TIME || type == ObProfileSchema::PASSWORD_GRACE_TIME)) {
      ret = OB_NOT_SUPPORTED;
      LOG_WARN("not support profile type", K(type));
    } else if (OB_FAIL(arg.schema_.set_value(type, schema_value))) {
      LOG_WARN("fail to set schema value", K(ret));
    }
  }
  return ret;
}

int ObUserProfileResolver::resolve(const ParseNode& parse_tree)
{
  int ret = OB_SUCCESS;
  stmt::StmtType stmt_type = stmt::T_NONE;
  CHECK_COMPATIBILITY_MODE(session_info_);
  ObUserProfileStmt* create_profile_stmt = NULL;
  if (2 != parse_tree.num_child_) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("expect 2 child, create profile type", "actual_num", parse_tree.num_child_, K(ret));
  } else if (OB_ISNULL(params_.session_info_) || OB_ISNULL(params_.allocator_)) {
    ret = OB_NOT_INIT;
    LOG_WARN("params not init", K(ret), KP(params_.allocator_), KP(params_.session_info_));
  } else if (!lib::is_oracle_mode()) {
    ret = OB_NOT_SUPPORTED;
    LOG_USER_ERROR(OB_NOT_SUPPORTED, "create profile except Oracle mode");
  } else if (OB_ISNULL(create_profile_stmt = create_stmt<ObUserProfileStmt>())) {
    ret = OB_ALLOCATE_MEMORY_FAILED;
    LOG_ERROR("Failed to create ObUserProfileStmt", K(ret));
  } else {
    // target
    obrpc::ObProfileDDLArg& arg = create_profile_stmt->get_ddl_arg();
    // tenant_id
    arg.schema_.set_tenant_id(params_.session_info_->get_effective_tenant_id());

    switch (parse_tree.type_) {
      case T_CREATE_PROFILE:
        arg.ddl_type_ = OB_DDL_CREATE_PROFILE;
        stmt_type = stmt::T_CREATE_PROFILE;
        break;
      case T_ALTER_PROFILE:
        arg.ddl_type_ = OB_DDL_ALTER_PROFILE;
        stmt_type = stmt::T_ALTER_PROFILE;
        break;
      case T_DROP_PROFILE:
        arg.ddl_type_ = OB_DDL_DROP_PROFILE;
        stmt_type = stmt::T_DROP_PROFILE;
        break;
      default:
        ret = OB_NOT_SUPPORTED;
        LOG_WARN("unsupported ddl type", K(ret), K(parse_tree.type_));
    }

    // profile name
    ParseNode* profile = NULL;
    if (OB_SUCC(ret)) {
      if (OB_ISNULL(profile = const_cast<ParseNode*>(parse_tree.children_[0]))) {
        ret = OB_ERR_UNEXPECTED;
        LOG_WARN("profile name node is null", K(ret));
      } else {
        arg.schema_.set_profile_name(ObString(profile->str_len_, profile->str_value_));
      }
      if (OB_SUCC(ret) && ObSchemaChecker::is_ora_priv_check()) {
        CK(params_.schema_checker_ != NULL);
        OZ(params_.schema_checker_->check_ora_ddl_priv(session_info_->get_effective_tenant_id(),
               session_info_->get_priv_user_id(),
               ObString(""),
               stmt_type,
               session_info_->get_enable_role_array()),
            session_info_->get_effective_tenant_id(),
            session_info_->get_user_id());
      }
    }

    // params
    ParseNode* param_list = NULL;
    if (OB_SUCC(ret) && T_DROP_PROFILE != parse_tree.type_) {
      if (OB_ISNULL(param_list = const_cast<ParseNode*>(parse_tree.children_[1])) ||
          param_list->type_ != T_PROFILE_PARAM_LIST) {
        ret = OB_ERR_UNEXPECTED;
        LOG_WARN("profile name node is null", K(ret), K(param_list));
      } else if (OB_FAIL(T_CREATE_PROFILE == parse_tree.type_ ? arg.schema_.set_default_values()
                                                              : arg.schema_.set_invalid_values())) {
        LOG_WARN("fail to set values", K(ret), K(parse_tree.type_));
      }

      ParseNode* param_pair = NULL;
      ParseNode* param_type = NULL;
      ParseNode* param_value = NULL;

      for (int64_t i = 0; OB_SUCC(ret) && i < param_list->num_child_; ++i) {
        if (OB_ISNULL(param_pair = param_list->children_[i])) {
          ret = OB_ERR_UNEXPECTED;
          LOG_WARN("param is null", K(ret));
        } else if (param_pair->type_ != T_PROFILE_PAIR || param_pair->num_child_ != 2) {
          ret = OB_ERR_UNEXPECTED;
          LOG_WARN("unexpected node type", K(ret), K(param_pair->type_), K(param_pair->num_child_), K(profile->type_));
        } else if (OB_ISNULL(param_type = param_pair->children_[0]) || param_type->type_ != T_INT) {
          ret = OB_ERR_UNEXPECTED;
          LOG_WARN("unexpected node", K(ret), KP(param_type));
        } else if (OB_ISNULL(param_value = param_pair->children_[1])) {
          ret = OB_ERR_UNEXPECTED;
          LOG_WARN("unexpected node", KP(ret));
        } else {
          ObObjParam numeric_value;
          ObString literal_prefix;
          const ObLengthSemantics length_semantics = params_.session_info_->get_local_nls_length_semantics();
          switch (param_value->type_) {
            case T_INT:
            case T_NUMBER:
            case T_DOUBLE:
            case T_FLOAT:
              if (OB_FAIL(ObResolverUtils::resolve_const(param_value,
                      create_profile_stmt->get_stmt_type(),
                      *params_.allocator_,
                      collation_type_,
                      params_.session_info_->get_nls_collation_nation(),
                      NULL,
                      numeric_value,
                      false,
                      literal_prefix,
                      length_semantics,
                      CS_TYPE_INVALID))) {
                LOG_WARN("fail to resolve const", K(ret));
              } else if (OB_FAIL(fill_arg(param_type->value_, numeric_value, arg))) {
                LOG_WARN("fail to fill arg", K(ret), K(param_type->value_));
              }
              break;
            case T_PROFILE_VERIFY_FUNCTION_NAME:
              if (OB_FAIL(resolver_password_verify_function(param_value, arg))) {
                LOG_WARN("fail to resolver verify function name", K(ret));
              }
              break;
            case T_PROFILE_UNLIMITED:
              if (OB_FAIL(arg.schema_.set_value(param_type->value_, INT64_MAX))) {
                LOG_WARN("fail to set schema value", K(ret));
              }
              break;
            case T_PROFILE_DEFAULT:
              if (OB_FAIL(arg.schema_.set_default_value(param_type->value_))) {
                LOG_WARN("fail to set schema default value", K(param_type->value_), K(ret));
              }
              break;
            default:
              ret = OB_ERR_UNEXPECTED;
              LOG_WARN("unknown value type", K(param_value->type_));
          }
        }
      }
    }

    LOG_DEBUG("profile ddl arg", K(ret), K(arg));
  }

  return ret;
}

int ObUserProfileResolver::resolver_password_verify_function(const ParseNode* node, obrpc::ObProfileDDLArg& arg)
{
  int ret = OB_SUCCESS;
  if (OB_ISNULL(node)) {
    ret = OB_ERR_UNEXPECTED;
    LOG_WARN("node is null", K(ret));
  } else if (1 != node->num_child_) {
    ret = OB_INVALID_ARGUMENT;
    LOG_WARN("expect 1 child", "actual_num", node->num_child_, K(ret));
  } else if (OB_ISNULL(node->children_[0])) {
    LOG_WARN("node is null", K(ret));
  } else {
    ObString password_verify_function(node->children_[0]->str_len_, node->children_[0]->str_value_);
    ObString package_name;
    bool exists = false;
    if (0 == password_verify_function.case_compare("NULL") || 0 == password_verify_function.length()) {
      /*do nothing*/
    } else if (!exists) {
      ret = OB_OBJECT_NAME_NOT_EXIST;
      LOG_USER_ERROR(OB_OBJECT_NAME_NOT_EXIST, "function");
      LOG_WARN("the function is not exist", K(ret));
    } else {
      arg.schema_.set_password_verify_function(password_verify_function);
    }
  }
  return ret;
}

}  // namespace sql
}  // namespace oceanbase
