/*-
 * ~~~~~~licensing~~~~~~
 * secure-vault-runtime
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.security.vault.runtime.resourcevault;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.entaxy.esb.resources.EntaxyResource;
import ru.entaxy.esb.resources.EntaxyResourceProvider;
import ru.entaxy.esb.resources.EntaxyResourceService;
import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.security.vault.runtime.EntaxyVault;
import ru.entaxy.security.vault.runtime.resourcevault.internal.ServiceHelper;

public class ResourceBasedVault implements EntaxyVault {

    private static final Logger LOG = LoggerFactory.getLogger(ResourceBasedVault.class);

    protected String resourceProtocol;
    protected EntaxyResourceProvider resourceProvider;
    protected EntaxyResourceService resourceService;
    protected String path;


    protected String password = "entaxy";
    protected StringEncryptor stringEncryptor;

    protected String getOrCreatePassword() {
        EntaxyResource resource = getResource(".pwd");
        if (!resource.exists()) {
            String pwd = generatePassword();
            try (InputStream is = new ByteArrayInputStream(pwd.getBytes())) {
                resource.save(is);
            } catch (Exception e) {
                LOG.error("Error storing password", e);
            }
            return pwd;
        }
        return resource.getAsString();
    }

    protected String generatePassword() {
        char[] possibleCharacters = (new String(
                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\\|;:\'\",<.>/?"))
                        .toCharArray();
        return RandomStringUtils.random(24, 0, possibleCharacters.length - 1, false, false, possibleCharacters,
                new SecureRandom());
    }

    public void init() {

        this.password = getOrCreatePassword();
        StandardPBEStringEncryptor enc = new StandardPBEStringEncryptor();
        enc.setAlgorithm("PBEWITHMD5ANDDES");
        enc.setPassword(password);
        // Fix NPE from Thread.currentThread().getContextClassLoader() in org.jasypt.normalization.Normalizer.initializeIcu4j()
        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
        enc.initialize();
        this.stringEncryptor = enc;

    }

    protected String encode(String value) {
        return this.stringEncryptor.encrypt(value);
    }

    protected String decode(String value) {
        return this.stringEncryptor.decrypt(value);
    }

    protected String getPathForAlias(String alias) {
        return String.format("%s:%s/%s", resourceProtocol, path, alias);
    }

    protected EntaxyResource getResource(String alias) {
        return resourceService.getResource(getPathForAlias(alias));
    }

    @Override
    public String getData(String alias) {
        EntaxyResource resource = getResource(alias);
        if (resource == null || !resource.exists())
            return null;
        return decode(resource.getAsString());
    }

    @Override
    public void setData(String alias, String value) {
        EntaxyResource resource = getResource(alias);
        try (InputStream is = new ByteArrayInputStream(encode(value).getBytes())) {
            resource.save(is);
        } catch (Exception e) {
            LOG.error(String.format("Error saving secure data [%s]", alias), e);
        }

    }

    @Override
    public void removeData(String alias) {
        EntaxyResource resource = getResource(alias);
        if (resource != null && resource.exists())
            resource.delete();
    }

    @Override
    public List<String> getAliases() {
        if (ServiceHelper.INSTANCE == null)
            return Collections.emptyList();
        return resourceService
                .getResources(String.format("%s:%s", resourceProtocol, path)).stream()
                .filter(res -> !res.getName().equals(".pwd")).map(res -> res.getName()).collect(Collectors.toList());
    }

    /* accessors */

    public String getResourceProtocol() {
        return resourceProtocol;
    }

    public void setResourceProtocol(String resourceProtocol) {
        this.resourceProtocol = resourceProtocol;
    }

    public EntaxyResourceService getResourceService() {
        return resourceService;
    }

    public void setResourceService(EntaxyResourceService resourceService) {
        this.resourceService = resourceService;
    }

    public void setResourceProvider(EntaxyResourceProvider resourceProvider) {
        this.resourceProvider = resourceProvider;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        if (!CommonUtils.isValid(path))
            this.path = ".vault";
        this.path = StringUtils.strip(path, "/");
    }

}
