/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.seatunnel.datasource.plugin.opcua;

import org.apache.seatunnel.api.configuration.util.OptionRule;
import org.apache.seatunnel.datasource.plugin.api.DataSourceChannel;
import org.apache.seatunnel.datasource.plugin.api.DataSourcePluginException;
import org.apache.seatunnel.datasource.plugin.api.model.TableField;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import org.eclipse.milo.opcua.stack.core.NamespaceTable;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;

import com.alibaba.fastjson2.JSONObject;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@Slf4j
public class OpcuaDataSourceChannel implements DataSourceChannel {

    private static final String DATABASE = "default";

    public static class Holder {
        private static final OpcuaDataSourceChannel INSTANCE = new OpcuaDataSourceChannel();
    }

    public static OpcuaDataSourceChannel getInstance() {
        return Holder.INSTANCE;
    }

    @Override
    public OptionRule getDataSourceOptions(@NonNull String pluginName) {
        return OpcuaOptionRule.optionRule();
    }

    @Override
    public OptionRule getDatasourceMetadataFieldsByDataSourceName(@NonNull String pluginName) {
        return OpcuaOptionRule.metadataRule();
    }

    @Override
    public List<String> getTables(
            @NonNull String pluginName,
            Map<String, String> requestParams,
            String database,
            Map<String, String> option) {
        //  checkArgument(StringUtils.equalsIgnoreCase(database, DATABASE), "database must be
        // default");
        try {
            if (requestParams.isEmpty()) {
                return new ArrayList<>();
            }
            OpcUaClientService opcClient = createOpcClient(requestParams);
            if (Objects.nonNull(opcClient)) {
                String ns = requestParams.get("ns") + "";
                String id = requestParams.get("id") + "";
                NodeId nodeId = new NodeId(UShort.valueOf(ns), id);
                return Arrays.asList(nodeId.toString());
            }
            return new ArrayList<>();
        } catch (Exception ex) {
            throw new DataSourcePluginException(
                    "check kafka connectivity failed, " + ex.getMessage(), ex);
        }
    }

    @Override
    public List<String> getDatabases(
            @NonNull String pluginName, @NonNull Map<String, String> requestParams) {
        String suffix = requestParams.get("suffix");
        if (StringUtils.isNotEmpty(suffix)) {
            return Arrays.asList(suffix);
        }

        return DEFAULT_DATABASES;
    }

    @Override
    public boolean checkDataSourceConnectivity(
            @NonNull String pluginName, @NonNull Map<String, String> requestParams) {
        try {
            OpcUaClientService opcClient = createOpcClient(requestParams);
            // just test the connection
            NamespaceTable namespaceTable = opcClient.getClient().getNamespaceTable();
            return Objects.nonNull(namespaceTable);
        } catch (Exception ex) {
            throw new DataSourcePluginException(
                    "check kafka connectivity failed, " + ex.getMessage(), ex);
        }
    }

    @Override
    public List<TableField> getTableFields(
            @NonNull String pluginName,
            @NonNull Map<String, String> requestParams,
            @NonNull String database,
            @NonNull String table) {
        // checkArgument(StringUtils.equalsIgnoreCase(database, DATABASE), "database must be
        // default");
        // checkArgument(StringUtils.equalsIgnoreCase(database, DATABASE), "database must be
        // default");
        try {
            List<TableField> tableFields = new ArrayList<>();
            OpcUaClientService opcClient = createOpcClient(requestParams);
            NodeId nodeId = null;
            // just test the connection
            if (StringUtils.isNotEmpty(table)) {
                try {
                    JSONObject jsonObject = JSONObject.parseObject(table);
                    UShort uShort = UShort.valueOf(jsonObject.getString("ns"));
                    Object id = jsonObject.get("id");
                    if (id instanceof Integer) {
                        nodeId = new NodeId(uShort, (Integer) id);
                    } else {
                        nodeId = new NodeId(uShort, id + "");
                    }
                } catch (Exception e) {
                    String[] split =
                            table.replace("NodeId{", "")
                                    .replace("{", "")
                                    .replace("}", "")
                                    .split(",");
                    UShort uShort = null;
                    String id = "";
                    for (String s : split) {
                        String[] split1 = s.split(":|=");
                        if (split1.length == 2) {

                            if (split1[0].toLowerCase().indexOf("ns") > -1) {
                                uShort = UShort.valueOf(split1[1]);
                            }

                            if (split1[0].toLowerCase().indexOf("id") > -1) {
                                id = split1[1];
                            }
                        }
                    }
                    if (uShort != null && StringUtils.isNotEmpty(id)) {
                        try {
                            // 如果是int
                            Integer idn = Integer.parseInt(id);
                            nodeId = new NodeId(uShort, idn);
                        } catch (Exception ex) {
                            nodeId = new NodeId(uShort, id);
                        }
                    }
                }
            }

            if (Objects.isNull(nodeId)) {
                return new ArrayList<>();
            }
            Object objects = opcClient.readNodeObject(nodeId);
            try {
                JSONObject jsonObject = JSONObject.parseObject(objects.toString());

                // 获取所有key的迭代器
                Iterator<String> keys = jsonObject.keySet().iterator();
                while (keys.hasNext()) {
                    String key = keys.next();
                    Object value = jsonObject.get(key);
                    TableField tableField = new TableField();
                    tableField.setName(key);
                    tableField.setType(value.getClass().getTypeName());
                    tableFields.add(tableField);
                }
                return tableFields;
            } catch (Exception e) {
                // 不是json
                TableField tableField = new TableField();
                tableField.setName(nodeId.getIdentifier() + "");
                tableField.setType("String");
                tableFields.add(tableField);
                return tableFields;
            }

        } catch (Exception ex) {
            throw new DataSourcePluginException(
                    "check mqtt getTableFields failed, " + ex.getMessage(), ex);
        }
    }

    @Override
    public Map<String, List<TableField>> getTableFields(
            @NonNull String pluginName,
            @NonNull Map<String, String> requestParams,
            @NonNull String database,
            @NonNull List<String> tables) {
        // checkArgument(StringUtils.equalsIgnoreCase(database, DATABASE), "database must be
        // default");
        Map<String, List<TableField>> map = new HashMap<>();
        if (CollectionUtils.isNotEmpty(tables)) {
            for (String table : tables) {
                map.put(table, getTableFields(pluginName, requestParams, database, table));
            }
        }

        return map;
    }

    private OpcUaClientService createOpcClient(Map<String, String> requestParams) {
        String host = requestParams.get("host") + "";
        String port = requestParams.get("port") + "";
        String suffix = requestParams.get("suffix") + "";

        try {
            OpcUaClientService opcUaClientService =
                    new OpcUaClientService(host, Integer.parseInt(port), suffix);
            return opcUaClientService;
        } catch (Exception e) {
            throw new DataSourcePluginException("OpcUa插件 创建链接错误！", e);
        }
    }
}
