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

package com.actiontech.dble.server.util;

import com.actiontech.dble.backend.mysql.CharsetUtil;
import com.actiontech.dble.config.ErrorCode;
import com.actiontech.dble.config.Isolations;
import com.actiontech.dble.config.model.SystemConfig;
import com.actiontech.dble.util.StringUtil;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.*;
import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlCharExpr;

import java.sql.SQLException;
import java.sql.SQLSyntaxErrorException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class SetItemUtil {

    private SetItemUtil() {
    }

    public static String getBooleanVal(String strKey, SQLExpr valueExpr) throws SQLException {
        String strValue = parseStringValue(valueExpr);
        switch (strValue) {
            case "1":
            case "on":
            case "true":
                return "true";
            case "0":
            case "off":
            case "false":
                return "false";
            default:
                throw new SQLException("Variable '" + strKey + "' can't be set to the value of '" + strValue + "'", "42000", ErrorCode.ER_WRONG_VALUE_FOR_VAR);
        }
    }

    public static String getIsolationVal(SQLExpr valueExpr) throws SQLSyntaxErrorException {
        if (valueExpr instanceof SQLBinaryOpExpr) {
            throw new SQLSyntaxErrorException("You have an error in your SQL syntax;");
        }
        String value = parseStringValue(valueExpr);
        switch (value) {
            case "read-uncommitted":
            case "read uncommitted":
                return Isolations.READ_UNCOMMITTED + "";
            case "read-committed":
            case "read committed":
                return Isolations.READ_COMMITTED + "";
            case "repeatable-read":
            case "repeatable read":
                return Isolations.REPEATABLE_READ + "";
            case "serializable":
                return Isolations.SERIALIZABLE + "";
            default:
                throw new SQLSyntaxErrorException("Variable 'tx_isolation|transaction_isolation' can't be set to the value of '" + value + "'");
        }
    }

    public static String getCharsetClientVal(SQLExpr valueExpr) throws SQLSyntaxErrorException {
        if (valueExpr instanceof SQLVariantRefExpr) {
            return valueExpr.toString();
        }

        String charsetClient = parseStringValue(valueExpr);
        if (charsetClient == null || charsetClient.equalsIgnoreCase("null")) {
            throw new SQLSyntaxErrorException("Variable 'character_set_client' can't be set to the value of 'NULL'");
        }

        if (checkCharset(charsetClient)) {
            if (!CharsetUtil.checkCharsetClient(charsetClient)) {
                throw new SQLSyntaxErrorException("Variable 'character_set_client' can't be set to the value of '" + charsetClient + "'");
            }
        } else {
            throw new SQLSyntaxErrorException("Unknown character set '" + charsetClient + "'");
        }
        return charsetClient;
    }

    public static String getCharsetResultsVal(SQLExpr valueExpr) throws SQLSyntaxErrorException {
        String charsetResult = parseStringValue(valueExpr);
        if (!charsetResult.equalsIgnoreCase("NULL") && !checkCharset(charsetResult)) {
            throw new SQLSyntaxErrorException("Unknown character set '" + charsetResult + "'");
        }
        return charsetResult;
    }

    public static String getCollationVal(SQLExpr valueExpr) throws SQLSyntaxErrorException {
        String collation = parseStringValue(valueExpr);
        if (!checkCollation(collation)) {
            throw new SQLSyntaxErrorException("Unknown collation '" + collation + "'");
        }
        return collation;
    }

    public static String getCharsetConnectionVal(SQLExpr valueExpr) throws SQLSyntaxErrorException {
        String charsetConnection = parseStringValue(valueExpr);
        if (charsetConnection.equals("null")) {
            throw new SQLSyntaxErrorException("Variable 'character_set_connection' can't be set to the value of 'NULL'");
        }
        String collationName = CharsetUtil.getDefaultCollation(charsetConnection);
        if (collationName == null) {
            throw new SQLSyntaxErrorException("Unknown character set '" + charsetConnection + "'");
        }
        return collationName;
    }

    public static String getCharsetVal(SQLExpr valueExpr) throws SQLSyntaxErrorException {
        String charsetValue = parseStringValue(valueExpr);
        if (charsetValue == null || charsetValue.equalsIgnoreCase("null")) {
            throw new SQLSyntaxErrorException("Unknown character set null");
        }
        String charset = getCharset(charsetValue);
        if (charset != null) {
            if (!CharsetUtil.checkCharsetClient(charset)) {
                throw new SQLSyntaxErrorException("Variable 'character set' can't be set to the value of '" + charset + "'");
            }
        } else {
            throw new SQLSyntaxErrorException("Unknown character set");
        }
        return charset;
    }

    private static boolean checkSetNamesSyntax(String stmt) {
        //druid parser can't find syntax error,use regex to check again, but it is not strict
        String regex = "set\\s+names\\s+[`']?[a-zA-Z_0-9]+[`']?(\\s+collate\\s+[`']?[a-zA-Z_0-9]+[`']?)?;?\\s*$";
        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        Matcher ma = pattern.matcher(stmt);
        return ma.find();
    }

    public static String getNamesVal(SQLExpr valueExpr) throws SQLSyntaxErrorException {
        String charset;
        String collate = null;
        if (valueExpr instanceof MySqlCharExpr) {
            MySqlCharExpr value = (MySqlCharExpr) valueExpr;
            charset = value.getText().toLowerCase();
            collate = StringUtil.removeBackQuote(value.getCollate()).toLowerCase();
        } else {
            charset = parseStringValue(valueExpr);
        }

        if (charset.toLowerCase().equals("default")) {
            charset = SystemConfig.getInstance().getCharset();
        } else {
            charset = StringUtil.removeApostropheOrBackQuote(charset.toLowerCase());
            if (!checkCharset(charset)) {
                throw new SQLSyntaxErrorException("Unknown character set  '" + charset + " or collate '" + collate + "'");
            }
        }
        if (collate == null) {
            collate = CharsetUtil.getDefaultCollation(charset);
        } else {
            collate = collate.toLowerCase();
            if (collate.equals("default")) {
                collate = CharsetUtil.getDefaultCollation(charset);
            } else {
                int collateIndex = CharsetUtil.getCollationIndexByCharset(charset, collate);
                if (collateIndex == 0) {
                    throw new SQLSyntaxErrorException("Unknown character set  '" + charset + " or collate '" + collate + "'");
                } else if (collateIndex < 0) {
                    throw new SQLSyntaxErrorException("COLLATION '" + collate + "' is not valid for CHARACTER SET '" + charset + "'");
                }
            }
        }

        if (!CharsetUtil.checkCharsetClient(charset)) {
            throw new SQLSyntaxErrorException("Variable 'names' can't be set to the value of '" + charset + "'");
        }

        return charset + ":" + collate;
    }

    private static String getCharset(String charset) {
        if (charset.toLowerCase().equals("default")) {
            charset = SystemConfig.getInstance().getCharset();
        }
        charset = StringUtil.removeApostropheOrBackQuote(charset.toLowerCase());
        if (checkCharset(charset)) {
            return charset;
        }
        return null;
    }

    private static boolean checkCharset(String name) {
        int ci = CharsetUtil.getCharsetDefaultIndex(name);
        return ci > 0;
    }

    private static boolean checkCollation(String collation) {
        int ci = CharsetUtil.getCollationIndex(collation);
        return ci > 0;
    }

    private static String parseStringValue(SQLExpr valueExpr) {
        String strValue = null;
        if (valueExpr instanceof SQLIdentifierExpr) {
            SQLIdentifierExpr value = (SQLIdentifierExpr) valueExpr;
            strValue = StringUtil.removeBackQuote(value.getSimpleName().toLowerCase());
        } else if (valueExpr instanceof SQLCharExpr) {
            SQLCharExpr value = (SQLCharExpr) valueExpr;
            strValue = value.getText().toLowerCase();
        } else if (valueExpr instanceof SQLIntegerExpr) {
            SQLIntegerExpr value = (SQLIntegerExpr) valueExpr;
            strValue = value.getNumber().toString();
        } else if (valueExpr instanceof SQLDefaultExpr || valueExpr instanceof SQLNullExpr ||
                valueExpr instanceof SQLVariantRefExpr) {
            strValue = valueExpr.toString();
        } else if (valueExpr instanceof SQLBooleanExpr) {
            // SQLBooleanExpr toString is xFalse,and druid 1.2.x fixed
            strValue = valueExpr.toString();
        }
        return strValue;
    }

    public static String parseVariablesValue(SQLExpr valueExpr) {
        String strValue;
        if (valueExpr instanceof SQLIdentifierExpr) {
            SQLIdentifierExpr value = (SQLIdentifierExpr) valueExpr;
            strValue = "'" + StringUtil.removeBackQuote(value.getSimpleName().toLowerCase()) + "'";
        } else if (valueExpr instanceof SQLCharExpr) {
            SQLCharExpr value = (SQLCharExpr) valueExpr;
            strValue = "'" + value.getText().toLowerCase() + "'";
        } else if (valueExpr instanceof SQLIntegerExpr) {
            SQLIntegerExpr value = (SQLIntegerExpr) valueExpr;
            strValue = value.getNumber().toString();
        } else if (valueExpr instanceof SQLNumberExpr) {
            SQLNumberExpr value = (SQLNumberExpr) valueExpr;
            strValue = value.getNumber().toString();
        } else if (valueExpr instanceof SQLBooleanExpr) {
            SQLBooleanExpr value = (SQLBooleanExpr) valueExpr;
            strValue = String.valueOf(value.getValue());
        } else if (valueExpr instanceof SQLDefaultExpr || valueExpr instanceof SQLNullExpr) {
            strValue = valueExpr.toString();
        } else {
            strValue = SQLUtils.toMySqlString(valueExpr);
        }
        return strValue;
    }

}
