/**
 * Copyright © 2012-2023 by Data Virtuality GmbH
 */
package org.teiid.translator.ws;

import com.datavirtuality.dv.core.interfaces.DVCaller;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;

import javax.activation.DataSource;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Dispatch;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.*;

/**
 * @author Allan Vital
 * @date 2023.09.20
 */
public class WsDataRetentionManager {

    private static WsDataRetentionManager INSTANCE;
    private static Random random = new Random();

    private WsDataRetentionManager() {}

    public static WsDataRetentionManager getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new WsDataRetentionManager();
        }
        return INSTANCE;
    }

    public DataSource logRequestAndReturnDataSource(Integer dataRetentionId, String requestEndpoint, DataSource dataSource, Dispatch<DataSource> dispatch) {
        return this.logAndReturnDataSource(dataRetentionId, "Endpoint: " + requestEndpoint, dispatch.getRequestContext(), dataSource, ".request");
    }

    public DataSource logResponseAndReturnDataSource(Integer dataRetentionId, Map<String, Object> responseHeaders, DataSource dataSource, Integer responseCode) {
        return this.logAndReturnDataSource(dataRetentionId, "Response code: " + responseCode, responseHeaders, dataSource, ".response");
    }

    private DataSource logAndReturnDataSource(Integer dataRetentionId, String firstLine, Map<String, Object> requestContext, DataSource dataSource, String extension) {
        String dataRetentionPath = this.getDataRetentionPath();
        if (dataRetentionPath == null || dataRetentionPath.isEmpty() || dataSource == null) {
            return dataSource;
        }
        LogManager.logInfo(LogConstants.CTX_WS, String.format("Logging %s of dataRetentionId: %s", extension, dataRetentionId));
        Map<String, Object> sanitizedRequestContext = cloneMapSanitizingSensibleValues(requestContext);
        List<String> requestContextLines = new LinkedList<>();
        requestContextLines.add(firstLine);
        for (Map.Entry<String, Object> entry : sanitizedRequestContext.entrySet()) {
            requestContextLines.add(entry.getKey() + ":" + entry.getValue());
        }
        try {
            Files.write(Paths.get(dataRetentionPath + File.separator + dataRetentionId + extension), requestContextLines, StandardCharsets.UTF_8);
            return logAndRecreateDataSource(dataSource, dataRetentionPath, dataRetentionId, extension);
        } catch (IOException e) {
            LogManager.logError(LogConstants.CTX_WS, "Error trying to log webservice data retention: " + e.getMessage() + " Stacktrace:" + Arrays.toString(e.getStackTrace()));
            return dataSource;
        }
    }

    private DataSource logAndRecreateDataSource(DataSource dataSource, String filePath, Integer dataRetentionId, String extension) throws IOException {
        String processingFilePath = filePath + File.separator + "executing";
        InputStream inputStream = dataSource.getInputStream();
        String dataFileFullPath = processingFilePath + File.separator  + dataRetentionId + extension;
        File file = new File(dataFileFullPath);
        if (!file.exists()) {
            LogManager.logDetail(LogConstants.CTX_WS, String.format("Creating folder and files for dataRetentionId %s and file %s", dataRetentionId, dataFileFullPath));
            File folder = new File(processingFilePath);
            folder.mkdirs();
            file.createNewFile();
        }
        try (FileWriter fileWriter = new FileWriter(file, false);
             InputStreamReader st = new InputStreamReader(inputStream);
             BufferedReader br = new BufferedReader(st)) {

            LogManager.logDetail(LogConstants.CTX_WS, String.format("Persisting data of dataRetentionId %s in temporary file %s", dataRetentionId, dataFileFullPath));
            int c;
            while ((c = br.read()) != -1) {
                fileWriter.append((char) c);
            }
        }
        File requestFile = new File(filePath + File.separator + dataRetentionId + extension);
        File dataFile = new File(dataFileFullPath);
        appendFileContentsToAnotherFile(requestFile, dataFile);
        LogManager.logDetail(LogConstants.CTX_WS, String.format("Recreating inputStream of dataRetentionId %s using the contents of file %s and marking it for deletion after usage", dataRetentionId, dataFileFullPath));
        return new DataSource() {
            @Override
            public InputStream getInputStream() throws IOException {
                return Files.newInputStream(dataFile.toPath(), StandardOpenOption.DELETE_ON_CLOSE);
            }

            @Override
            public OutputStream getOutputStream() throws IOException {
                return dataSource.getOutputStream();
            }

            @Override
            public String getContentType() {
                return dataSource.getContentType();
            }

            @Override
            public String getName() {
                return dataSource.getName();
            }
        };
    }

    private void appendFileContentsToAnotherFile(File toAppend, File contentsToAppend) throws IOException {
        try (FileReader fr = new FileReader(contentsToAppend); FileWriter fw = new FileWriter(toAppend,true)) {
            LogManager.logDetail(LogConstants.CTX_WS, String.format("Persisting final data in file: %s", toAppend));
            int read = fr.read();
            fw.write(System.lineSeparator());
            while (read != -1) {
                fw.write(read);
                read = fr.read();
            }
        }
    }

    private String getDataRetentionPath() {
        try {
            return (String) DVCaller.callWithExc(240);
        } catch (Throwable e) {
            LogManager.logError(LogConstants.CTX_WS, "Error trying to get the path to execute the data retention of WebServices: " + e.getMessage() + " Stacktrace:" + Arrays.toString(e.getStackTrace()));
            return "";
        }
    }

    public static Integer generateDatRetentionId() {
        return random.nextInt(Integer.MAX_VALUE);
    }

    public static Map<String, Object> cloneMapSanitizingSensibleValues(Map<String, Object> map) {
        Map<String, Object> sanitizedMap = new HashMap<>(map);
        if(sanitizedMap.containsKey(BindingProvider.PASSWORD_PROPERTY)) {
            sanitizedMap.put(BindingProvider.PASSWORD_PROPERTY, "***");
        }
        return sanitizedMap;
    }

}