package com.banesco.common.application.helper;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.util.internal.StringUtil;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.util.*;

@Slf4j
@ApplicationScoped
@RegisterForReflection
public class SerializationHelper {

    private final ObjectMapper objectMapper;

    @Inject
    public SerializationHelper(
        ObjectMapper objectMapper
    ) {
        this.objectMapper = objectMapper;
    }

    public String encodeBase64(Object object) {
        try {
            String jsonString = objectMapper.writeValueAsString(object);

            return Base64.getEncoder().encodeToString(jsonString.getBytes());
        } catch (Exception e) {
            log.error("Error al codificar a Base64: {}", e.getMessage());
            return null;
        }
    }

    public String encodeStringToBase64(String text) {
        if (StringUtil.isNullOrEmpty(text)) {
            return null;
        }

        return Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
    }

    public <T> T decodeBase64(String base64String, Class<T> clazz) {
        try {
            byte[] decodedBytes = Base64.getDecoder().decode(base64String);
            String jsonString = new String(decodedBytes);

            return objectMapper.readValue(jsonString, clazz);
        } catch (Exception e) {
            log.error("Error al decodificar desde Base64: {}", e.getMessage());
            return null;
        }
    }

    public String encodeSha256(String json) {
        return DigestUtils.sha256Hex(json);
    }

    public <T> String toJsonString(T element) {
        if (element == null) {
            return "";
        }

        try {
            return objectMapper.writeValueAsString(element);
        } catch (JsonProcessingException e) {
            log.error("Error al convertir objeto a Json String: {}", e.getMessage());
            return "";
        }
    }

    public <T> Map<String, Object> toMap(T element) {
        return toMap(element, null);
    }

    public <T> Map<String, Object> toMap(
        T element,
        List<String> excludedFields
    ) {
        if (element == null) {
            return new HashMap<>();
        }

        try {
            Map<String, Object> map = objectMapper.convertValue(
                element, new TypeReference<>() {}
            );

            if (excludedFields != null && !excludedFields.isEmpty()) {
                Set<String> excludedSet = new HashSet<>(excludedFields);
                excludedSet.forEach(map::remove);
            }

            return map;
        } catch (Exception e) {
            log.error("Error al convertir objeto a Map: {}", e.getMessage());
            return new HashMap<>();
        }
    }

    public <T> Map<String, Object> toTreeMap(T element) {
        return toTreeMap(element, null);
    }

    public <T> Map<String, Object> toTreeMap(
        T element,
        List<String> excludedFields
    ) {
        if (element == null) {
            return new TreeMap<>();
        }

        try {
            Map<String, Object> tempMap = objectMapper.convertValue(
                element, new TypeReference<>() {}
            );

            Map<String, Object> treeMap = new TreeMap<>(tempMap);

            if (excludedFields != null && !excludedFields.isEmpty()) {
                Set<String> excludedSet = new HashSet<>(excludedFields);
                excludedSet.forEach(treeMap::remove);
            }

            return treeMap;
        } catch (Exception e) {
            log.error("Error al convertir objeto a TreeMap: {}", e.getMessage());
            return new TreeMap<>();
        }
    }

    public String generateSignature(
        Object operation,
        String channelOrigin
    ) {
        Map<String, Object> operationSortedMap = toTreeMap(operation);
        StringBuilder concatenatedValues = new StringBuilder();
        String channelBase64 = encodeStringToBase64(channelOrigin);

        for (Object value : operationSortedMap.values()) {
            if(!Objects.isNull(value)) {
                concatenatedValues.append(value);
            }
        }

        String finalString = concatenatedValues + channelBase64;

        logToShow(finalString, channelBase64);

        return encodeSha256(finalString);
    }

    public String generateSignature(
        Long id,
        String channelOrigin
    ) {
        String channelBase64 = encodeStringToBase64(channelOrigin);
        String finalString = id + channelBase64;

        logToShow(finalString, channelBase64);

        return encodeSha256(finalString);
    }

    public String generateSignature(
        Map<String, Object> global,
        List<Map<String, Object>> requests,
        String channelOrigin
    ) {
        StringBuilder concatenatedValues = new StringBuilder();
        String channelBase64 = encodeStringToBase64(channelOrigin);

        requests.forEach(item -> {
            if(!Objects.isNull(item)) {
                for (Object value : item.values()) {
                    if(!Objects.isNull(value)) {
                        concatenatedValues.append(value);
                    }
                }
            }
        });

        for (Object value : global.values()) {
            if(!Objects.isNull(value)) {
                concatenatedValues.append(value);
            }
        }

        String finalString = concatenatedValues + channelBase64;

        logToShow(concatenatedValues.toString(), channelBase64);

        return encodeSha256(finalString);
    }

    private void logToShow(
        String concatenatedValues,
        String channelBase64
    ) {
        log.info("1. Operation concatenando valores: {}", concatenatedValues);
        log.info("2. Channel Origin codificado: {}", channelBase64);
    }
}
