/*
 * 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.s3;

import org.apache.seatunnel.api.configuration.util.OptionRule;
import org.apache.seatunnel.common.utils.SeaTunnelException;
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.lang3.StringUtils;

import com.alibaba.fastjson2.JSON;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.ListObjectsArgs;
import io.minio.MinioClient;
import io.minio.Result;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.MinioException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.NonNull;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class S3DatasourceChannel implements DataSourceChannel {
    private S3ClientService s3ClientService;

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

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

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

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

    @Override
    public List<String> getTables(
            @NonNull String pluginName,
            Map<String, String> requestParams,
            String database,
            Map<String, String> options) {
        createS3Client(requestParams);
        MinioClient minioClient = s3ClientService.getMinioClient();
        List<String> tab = new ArrayList<>();
        try {
            boolean b =
                    minioClient.bucketExists(BucketExistsArgs.builder().bucket(database).build());
            if (!b) {
                return tab;
                //                throw new MinioException("桶不存在");
            }
            Iterable<Result<Item>> results = getFileByDir(minioClient, database, null);
            results.forEach(
                    x -> {
                        try {
                            boolean dir = x.get().isDir();
                            String s = x.get().objectName();
                            if (!dir) {
                                tab.add(s);
                            } else {
                                getAllFile(
                                        tab,
                                        getFileByDir(minioClient, database, s),
                                        minioClient,
                                        database);
                            }
                        } catch (ErrorResponseException
                                | InsufficientDataException
                                | InternalException
                                | InvalidKeyException
                                | InvalidResponseException
                                | IOException
                                | NoSuchAlgorithmException
                                | ServerException
                                | XmlParserException e) {
                            throw new RuntimeException(e);
                        }
                    });
            return tab;

        } catch (InvalidKeyException | IOException | NoSuchAlgorithmException | MinioException e) {
            throw new RuntimeException(e);
        }

        //        throw new UnsupportedOperationException("getTables is not supported for S3
        // datasource");
    }

    private void getAllFile(
            List<String> tab,
            Iterable<Result<Item>> results,
            MinioClient minioClient,
            String database) {
        results.forEach(
                x -> {
                    try {
                        boolean dir = x.get().isDir();
                        String s = x.get().objectName();
                        if (!dir) {
                            tab.add(s);
                        } else {
                            this.getAllFile(
                                    tab,
                                    getFileByDir(minioClient, database, s),
                                    minioClient,
                                    database);
                        }
                    } catch (ErrorResponseException
                            | InsufficientDataException
                            | InternalException
                            | InvalidKeyException
                            | InvalidResponseException
                            | IOException
                            | NoSuchAlgorithmException
                            | ServerException
                            | XmlParserException e) {
                        throw new RuntimeException(e);
                    }
                });
    }

    private Iterable<Result<Item>> getFileByDir(
            @NonNull MinioClient minioClient, @NonNull String bucket, String dir) {
        Iterable<Result<Item>> results;
        if (StringUtils.isEmpty(dir)) {
            results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucket).build());
        } else {
            results =
                    minioClient.listObjects(
                            ListObjectsArgs.builder().bucket(bucket).prefix(dir).build());
        }
        return results;
    }

    @Override
    public List<String> getDatabases(
            @NonNull String pluginName, @NonNull Map<String, String> requestParams) {
        createS3Client(requestParams);
        MinioClient minioClient = s3ClientService.getMinioClient();
        List<String> db = new ArrayList<>();
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            buckets.forEach(
                    x -> {
                        String name = x.name();
                        db.add(name);
                    });
            return db;
        } catch (ServerException
                | ErrorResponseException
                | InsufficientDataException
                | IOException
                | NoSuchAlgorithmException
                | InvalidKeyException
                | InvalidResponseException
                | XmlParserException
                | InternalException e) {
            throw new RuntimeException(e);
        }
        //        throw new UnsupportedOperationException("getDatabases is not supported for S3
        // datasource");
    }

    @Override
    public boolean checkDataSourceConnectivity(
            @NonNull String pluginName, @NonNull Map<String, String> requestParams) {
        if (requestParams.isEmpty()) {
            throw new SeaTunnelException("requestParmas 为空！");
        }
        try {
            createS3Client(requestParams);
            return true;
        } catch (Exception ex) {
            throw new DataSourcePluginException(
                    "check s3 connectivity failed, " + ex.getMessage(), ex);
        }
        //        try (FileSystem fs = FileSystem.get(conf)) {
        ////            fs.listStatus(new Path("/"));
        //
        //            return true;
        //        } catch (IOException e) {
        //            throw new DataSourcePluginException(
        //                    String.format("check s3 connectivity failed, config is: %s",
        // requestParams), e);
        //        }
    }

    @Override
    public List<TableField> getTableFields(
            @NonNull String pluginName,
            @NonNull Map<String, String> requestParams,
            @NonNull String database,
            @NonNull String table) {
        createS3Client(requestParams);
        MinioClient minioClient = s3ClientService.getMinioClient();
        List<TableField> tableFields = getObject(minioClient, table, database, requestParams);
        return tableFields;
        //        throw new UnsupportedOperationException(
        //                "getTableFields is not supported for S3 datasource");
    }

    @Override
    public Map<String, List<TableField>> getTableFields(
            @NonNull String pluginName,
            @NonNull Map<String, String> requestParams,
            @NonNull String database,
            @NonNull List<String> tables) {
        createS3Client(requestParams);
        MinioClient minioClient = s3ClientService.getMinioClient();
        Map<String, List<TableField>> stringList = new HashMap<>();
        tables.forEach(
                tab -> {
                    List<TableField> tableFields =
                            getObject(minioClient, tab, database, requestParams);
                    stringList.put(tab, tableFields);
                });
        return stringList;
        //        throw new UnsupportedOperationException(
        //                "getTableFields is not supported for S3 datasource");
    }

    private List<TableField> getObject(
            MinioClient minioClient,
            String table,
            String database,
            Map<String, String> requestParams) {
        List<TableField> all = new ArrayList<>();
        try {
            GetObjectResponse minioClientObject =
                    minioClient.getObject(
                            GetObjectArgs.builder().object(table).bucket(database).build());
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(minioClientObject));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                if (table.endsWith("csv")) {
                    String delimiter = requestParams.get("delimiter");
                    String[] split = line.split(delimiter);
                    Class<TableField> tableFieldClass = TableField.class;
                    Field[] declaredFields = tableFieldClass.getDeclaredFields();
                    HashMap<String, Object> map = new HashMap<>();
                    for (int i = 0; i < split.length; i++) {
                        String name = declaredFields[i].getName();
                        map.put(name, split[i]);
                    }
                    TableField tableField =
                            JSON.parseObject(JSON.toJSONString(map), TableField.class);
                    all.add(tableField);
                }
            }
            System.out.println(
                    all + "==================================================================");
        } catch (ErrorResponseException
                | InsufficientDataException
                | InternalException
                | InvalidKeyException
                | InvalidResponseException
                | IOException
                | NoSuchAlgorithmException
                | ServerException
                | XmlParserException e) {
            throw new RuntimeException(e);
        }
        return all;
    }

    public S3ClientService createS3Client(Map<String, String> requestParams) {
        int i = requestParams.get("fs.s3a.endpoint").lastIndexOf(":");
        String endpoint = requestParams.get("fs.s3a.endpoint");
        Integer port =
                Integer.valueOf(
                        requestParams.get("fs.s3a.endpoint").substring(i + 1).replace("/", ""));
        String provider = requestParams.get("fs.s3a.aws.credentials.provider");
        String username = requestParams.get("access_key");
        String password = requestParams.get("secret_key");
        String bucket = requestParams.get("bucket");
        try {
            s3ClientService =
                    new S3ClientService(endpoint, provider, username, password, port, bucket);
            return s3ClientService;
        } catch (Exception e) {
            throw new SeaTunnelException("创建Mqtt客户端错误！");
        }
    }
}
