/*
 * Copyright (C) 2016-2022 ActionTech.
 * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
 */

package com.actiontech.dble.config.model.user;

import com.actiontech.dble.DbleServer;
import com.actiontech.dble.config.ErrorCode;
import com.actiontech.dble.config.model.sharding.SchemaConfig;
import com.actiontech.dble.server.util.SchemaUtil;
import com.actiontech.dble.util.StringUtil;
import com.alibaba.druid.wall.WallProvider;

import java.sql.SQLException;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class ShardingUserConfig extends ServerUserConfig {
    private final boolean readOnly;
    private Set<String> schemas;
    private final UserPrivilegesConfig privilegesConfig;

    public ShardingUserConfig(UserConfig user, String tenant, WallProvider blacklist, boolean readOnly, Set<String> schemas, UserPrivilegesConfig privilegesConfig) {
        super(user, tenant, blacklist);
        this.readOnly = readOnly;
        this.schemas = schemas;
        this.privilegesConfig = privilegesConfig;
    }

    public boolean isReadOnly() {
        return readOnly;
    }


    public Set<String> getSchemas() {
        return schemas;
    }


    public UserPrivilegesConfig getPrivilegesConfig() {
        return privilegesConfig;
    }

    public void changeMapToLowerCase() {
        Set<String> newSchemas = new HashSet<>();
        for (String schemaName : schemas) {
            newSchemas.add(schemaName.toLowerCase());
        }
        schemas = newSchemas;
    }

    @Override
    public void isValidSchemaInfo(UserName user, SchemaUtil.SchemaInfo schemaInfo) throws SQLException {
        if (!schemaInfo.isDual() && !SchemaUtil.MYSQL_SYS_SCHEMA.contains(schemaInfo.getSchema().toUpperCase())) {
            SchemaConfig schemaConfig = DbleServer.getInstance().getConfig().getSchemas().get(schemaInfo.getSchema());
            if (schemaConfig == null) {
                String msg = "Table " + StringUtil.getFullName(schemaInfo.getSchema(), schemaInfo.getTable()) + " doesn't exist";
                throw new SQLException(msg, "42S02", ErrorCode.ER_NO_SUCH_TABLE);
            }
            if (!schemas.contains(schemaInfo.getSchema())) {
                String msg = "Access denied for user '" + user.getFullName() + "' to database '" + schemaInfo.getSchema() + "'";
                throw new SQLException(msg, "HY000", ErrorCode.ER_DBACCESS_DENIED_ERROR);
            }
            schemaInfo.setSchemaConfig(schemaConfig);
        }
    }

    public boolean equalsBaseInfo(ShardingUserConfig shardingUserConfig) {
        return super.equalsBaseInfo(shardingUserConfig) &&
                this.readOnly == shardingUserConfig.isReadOnly() &&
                this.schemas.equals(shardingUserConfig.getSchemas()) &&
                isEquals(this.privilegesConfig, shardingUserConfig.getPrivilegesConfig());
    }

    private boolean isEquals(UserPrivilegesConfig o1, UserPrivilegesConfig o2) {
        if (o1 == null) {
            return o2 == null;
        }
        return o1 == o2 || o1.equalsBaseInfo(o2);
    }

    @Override
    public int checkSchema(String schema) {
        if (schema == null) {
            return 0;
        }
        if (DbleServer.getInstance().getSystemVariables().isLowerCaseTableNames()) {
            schema = schema.toLowerCase();
        }
        if (!DbleServer.getInstance().getConfig().getSchemas().containsKey(schema)) {
            return ErrorCode.ER_BAD_DB_ERROR;
        }
        if (schemas.contains(schema)) {
            return 0;
        } else {
            return ErrorCode.ER_DBACCESS_DENIED_ERROR;
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        ShardingUserConfig that = (ShardingUserConfig) o;
        return readOnly == that.readOnly &&
                Objects.equals(schemas, that.schemas) &&
                isEquals(privilegesConfig, that.privilegesConfig);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), readOnly, schemas, privilegesConfig);
    }
}
