package com.njcn.gather.systemops.database.component;

import cn.hutool.core.util.StrUtil;
import com.njcn.gather.systemops.database.config.DbmsProperties;
import com.njcn.gather.systemops.database.pojo.po.DatabaseConnection;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * Oracle Data Pump 命令执行组件。
 */
@Component
@RequiredArgsConstructor
public class DataPumpCommandExecutor {

    private final DbmsProperties dbmsProperties;

    public CommandResult expdp(DatabaseConnection connection, String password, String directoryName,
                               String dumpFileName, String logFileName, List<String> tableNames) {
        List<String> command = new ArrayList<>();
        command.add(resolveTool(dbmsProperties.getTools().getExpdpPath(), "expdp"));
        fillCommonArgs(command, connection, password, directoryName, dumpFileName, logFileName);
        if (tableNames != null && !tableNames.isEmpty()) {
            command.add("tables=" + connection.getSchemaName() + "." + String.join("," + connection.getSchemaName() + ".", tableNames));
        }
        return execute(command);
    }

    public CommandResult impdp(DatabaseConnection connection, String password, String directoryName,
                               String dumpFileName, String logFileName, String tableExistsAction) {
        List<String> command = new ArrayList<>();
        command.add(resolveTool(dbmsProperties.getTools().getImpdpPath(), "impdp"));
        fillCommonArgs(command, connection, password, directoryName, dumpFileName, logFileName);
        if (StrUtil.isNotBlank(tableExistsAction)) {
            command.add("table_exists_action=" + tableExistsAction);
        }
        return execute(command);
    }

    private void fillCommonArgs(List<String> command, DatabaseConnection connection, String password,
                                String directoryName, String dumpFileName, String logFileName) {
        command.add(connection.getUsername() + "/" + password + "@" + buildConnectIdentifier(connection));
        command.add("directory=" + directoryName);
        command.add("dumpfile=" + dumpFileName);
        command.add("logfile=" + logFileName);
    }

    private String buildConnectIdentifier(DatabaseConnection connection) {
        if (StrUtil.isNotBlank(connection.getServiceName())) {
            return "//" + connection.getHost() + ":" + connection.getPort() + "/" + connection.getServiceName();
        }
        return connection.getHost() + ":" + connection.getPort() + ":" + connection.getSid();
    }

    private String resolveTool(String configuredPath, String defaultName) {
        return StrUtil.blankToDefault(configuredPath, defaultName);
    }

    private CommandResult execute(List<String> command) {
        CommandResult result = new CommandResult();
        result.setCommand(maskPassword(command));
        try {
            Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
            StringBuilder output = new StringBuilder();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    output.append(line).append(System.lineSeparator());
                }
            }
            int exitCode = process.waitFor();
            result.setExitCode(exitCode);
            result.setOutput(output.toString());
            result.setSuccess(exitCode == 0);
        } catch (Exception exception) {
            result.setExitCode(-1);
            result.setOutput(exception.getMessage());
            result.setSuccess(false);
        }
        return result;
    }

    private String maskPassword(List<String> command) {
        if (command.size() < 2) {
            return String.join(" ", command);
        }
        List<String> masked = new ArrayList<>(command);
        String credential = masked.get(1);
        int slashIndex = credential.indexOf('/');
        int atIndex = credential.indexOf('@');
        if (slashIndex > 0 && atIndex > slashIndex) {
            masked.set(1, credential.substring(0, slashIndex + 1) + "******" + credential.substring(atIndex));
        }
        return String.join(" ", masked);
    }

    @Data
    public static class CommandResult {
        private Boolean success;
        private Integer exitCode;
        private String command;
        private String output;
    }
}
