package nl.htm.ovpay.abt;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;

public class ABTProductsPUTGenerator {
    private static final Logger LOGGER = LoggerFactory.getLogger(ABTProductsPUTGenerator.class);

    public static void main(String[] args) throws Exception {
        LOGGER.info("Starting ABTProductsPUTGenerator with arguments {}", Arrays.stream(args).toList());
        if (args.length > 0 && args[0].equalsIgnoreCase("clearAttribute")) {
            clearAttribute(args);
        } else {
            generateOutput(args);
        }
    }

    private static void generateOutput(String[] args) throws Exception {
        if (args.length != 2) {
            LOGGER.info("To modify input/output path, use: java -jar ABTProductsPUTGenerator.jar <inputPath> <outputPath>");
        }
        var inputFile = args.length > 0 ? args[0] : "input.json";
        var outputFile = args.length > 1 ? args[1] : "output.json";
        try (InputStream is = getInputStream(inputFile)) {
            ObjectMapper mapper = new ObjectMapper();
            JsonNode jsonNode = mapper.readTree(is);
            Long productId = jsonNode.get("productId").asLong();

            LOGGER.info("Successfully parsed product with productId {} from JSON:\n{}\n", productId, jsonNode.toPrettyString());

            JsonNode putJsonNode = processJsonNode(jsonNode);

            writeToFile(putJsonNode, outputFile);

            LOGGER.info("DONE! Modify the output JSON as desired and send it as ABTProducts PUT request body:");
            LOGGER.info("curl -X PUT -H 'Content-Type: application/json' {baseUrl}/abt/abtproducts/1.0/products/{} --data @{}", productId, outputFile);
        }
    }

    private static void clearAttribute(String[] args) throws Exception {
        if (args.length != 5) {
            LOGGER.error("Incorrect input parameters!");
            LOGGER.error("To clear attribute, use: java -jar ABTProductsPUTGenerator.jar clearAttribute <inputPath> <attributeToClear> <environment> <wso2BearerToken>");
            return;
        }

        var inputFile = args[1];
        var attributeToClear = args[2];
        var environment = args[3];
        var wso2BearerToken = args[4];

        try (InputStream is = getInputStream(inputFile)) {
            ObjectMapper mapper = new ObjectMapper();
            JsonNode jsonNode = mapper.readTree(is);
            var productIds = jsonNode.findValues("productId").stream().filter(JsonNode::isValueNode).map(JsonNode::asText).toList();

            LOGGER.info("Found productIds to process: {}", productIds);
            LOGGER.warn("Are you SURE you want to set attribute \"{}\" for all these productIds on environment {} to NULL?", attributeToClear, environment);
            LOGGER.warn("Type 'yes' to continue...");
            var input = new Scanner(System.in).nextLine();
            if (!input.equalsIgnoreCase("yes")) {
                LOGGER.info("Aborting...");
                return;
            }

            for (var productId : productIds) {
                LOGGER.info("Getting product details for product {}...", productId);
                var productJsonString = APIHelper.getProductDetails(environment, productId, wso2BearerToken);
                var productJsonNode = mapper.readTree(productJsonString);
                var putJsonNode = processJsonNode(productJsonNode);
                LOGGER.info("Clearing attribute \"{}\" from product with productId {}...", attributeToClear, productId);
                ((ObjectNode)putJsonNode).putRawValue(attributeToClear, null);

                LOGGER.info("PUT product details for product with productId {} with cleared attribute \"{}\"...", productId, attributeToClear);
                LOGGER.info("PUT product details with JSON body: {}", putJsonNode.toPrettyString());
                APIHelper.putProductDetails(environment, productId, putJsonNode.toString(), wso2BearerToken);
            }

            LOGGER.info("DONE clearing attribute \"{}\" for productIds {}!", attributeToClear, productIds);
        }
    }

    private static InputStream getInputStream(String filePath) throws IOException {
        var externalResource = new File(filePath);
        if (externalResource.exists()) {
            LOGGER.info("Loading ABTProducts details JSON from external file {}...", externalResource.getAbsolutePath());
            return externalResource.toURI().toURL().openStream();
        } else {
            LOGGER.info("External file {} not found, using internal resource /input.json...", filePath);
            return ABTProductsPUTGenerator.class.getResourceAsStream("/input.json");
        }
    }

    private static void writeToFile(JsonNode jsonNode, String filePath) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        var outputFile = new File(filePath);
        if (outputFile.exists()) {
            LOGGER.info("Deleting existing output file {}...", filePath);
            outputFile.delete();
        }
        mapper.writerWithDefaultPrettyPrinter().writeValue(outputFile, jsonNode);
        LOGGER.info("Successfully wrote JSON to file {}!", outputFile.getAbsolutePath(), jsonNode.toPrettyString());
    }

    private static JsonNode processJsonNode(JsonNode jsonNode) {
        var newJsonNode = new ObjectNode(new JsonNodeFactory(true));

        jsonNode.fields().forEachRemaining(jsonField -> {
            if (!List.of(JsonNodeType.ARRAY, JsonNodeType.OBJECT).contains(jsonField.getValue().getNodeType())) {
                if (jsonField.getValue().isNull()) {
                    checkRewriteNullFields(jsonField, newJsonNode);
                } else if (!jsonField.getKey().equals("productId") && !jsonField.getKey().equals("auditTrail")) {
                    LOGGER.info("Keeping {} as-is...", jsonField.getKey());
                    newJsonNode.put(jsonField.getKey(), jsonField.getValue());
                }
            } else {
                if (List.of("validityPeriod", "productTranslations").contains(jsonField.getKey())) {
                    LOGGER.info("Keeping {} as-is...", jsonField.getKey());
                    newJsonNode.set(jsonField.getKey(), jsonField.getValue());
                } else {
                    rewriteObjectFields(jsonField, newJsonNode);
                    rewriteArrayFields(jsonField, newJsonNode);
                }
            }
        });

        LOGGER.info("Successfully rewritten into PUT JSON:\n{}\n", newJsonNode.toPrettyString());

        return newJsonNode;
    }

    private static void checkRewriteNullFields(Map.Entry<String, JsonNode> jsonField, JsonNode newJsonNode) {
        switch (jsonField.getKey()) {
            case "productOwner" -> {
                LOGGER.info("Rewriting null productOwner to productOwnerId...");
                ((ObjectNode)newJsonNode).putRawValue("productOwnerId", null);
            }
            case "productCategory" -> {
                LOGGER.info("Rewriting null productCategory to productCategoryId...");
                ((ObjectNode)newJsonNode).putRawValue("productCategoryId", null);
            }
            case "paymentMoment" -> {
                LOGGER.info("Rewriting null paymentMoment to paymentMomentId...");
                ((ObjectNode)newJsonNode).putRawValue("paymentMomentId", null);
            }
            case "requiredCustomerLevel" -> {
                LOGGER.info("Rewriting null requiredCustomerLevel to requiredCustomerLevelId...");
                ((ObjectNode)newJsonNode).putRawValue("requiredCustomerLevelId", null);
            }
            case "layerInfo" -> {
                LOGGER.info("Rewriting null layerInfo to layerInfoId...");
                ((ObjectNode)newJsonNode).putRawValue("layerInfoId", null);
            }
            case "marketSegments" -> {
                LOGGER.info("Rewriting null marketSegments to marketSegmentIds...");
                ((ObjectNode)newJsonNode).putRawValue("marketSegmentIds", null);
            }
            case "customerSegments" -> {
                LOGGER.info("Rewriting null customerSegments to customerSegmentIds...");
                ((ObjectNode)newJsonNode).putRawValue("customerSegmentIds", null);
            }
            case "allowedGboAgeProfiles" -> {
                LOGGER.info("Rewriting null allowedGboAgeProfiles to allowedGboAgeProfileIds...");
                ((ObjectNode)newJsonNode).putRawValue("allowedGboAgeProfileIds", null);
            }
            case "mandatoryCustomerDataItems" -> {
                LOGGER.info("Rewriting null mandatoryCustomerDataItems to mandatoryCustomerDataItemIds...");
                ((ObjectNode)newJsonNode).putRawValue("mandatoryCustomerDataItemIds", null);
            }
            case "requiredGboPersonalAttributes" -> {
                LOGGER.info("Rewriting null requiredGboPersonalAttributes to requiredGboPersonalAttributeIds...");
                ((ObjectNode)newJsonNode).putRawValue("requiredGboPersonalAttributeIds", null);
            }
            case "tokenTypes" -> {
                LOGGER.info("Rewriting null tokenTypes to tokenTypeIds...");
                ((ObjectNode)newJsonNode).putRawValue("tokenTypeIds", null);
            }
            case "serviceOptions" -> {
                LOGGER.info("Rewriting null serviceOptions to serviceOptionIds...");
                ((ObjectNode)newJsonNode).putRawValue("serviceOptionIds", null);
            }
            default -> {
                LOGGER.info("Keeping {} as-is...", jsonField.getKey());
                ((ObjectNode)newJsonNode).put(jsonField.getKey(), jsonField.getValue());
            }
        }
    }

    private static void rewriteObjectFields(Map.Entry<String, JsonNode> jsonField, JsonNode newJsonNode) {
        switch (jsonField.getKey()) {
            case "productOwner" -> {
                LOGGER.info("Rewriting productOwner to productOwnerId...");
                Long productOwnerId = jsonField.getValue().get("productOwnerId").asLong();
                ((ObjectNode)newJsonNode).put("productOwnerId", productOwnerId);
            }
            case "productCategory" -> {
                LOGGER.info("Rewriting productCategory to productCategoryId...");
                Long productCategoryId = jsonField.getValue().get("productCategoryId").asLong();
                ((ObjectNode)newJsonNode).put("productCategoryId", productCategoryId);
            }
            case "paymentMoment" -> {
                LOGGER.info("Rewriting paymentMoment to paymentMomentId...");
                Long paymentMomentId = jsonField.getValue().get("paymentMomentId").asLong();
                ((ObjectNode)newJsonNode).put("paymentMomentId", paymentMomentId);
            }
            case "requiredCustomerLevel" -> {
                LOGGER.info("Rewriting requiredCustomerLevel to requiredCustomerLevelId...");
                Long requiredCustomerLevelId = jsonField.getValue().get("requiredCustomerLevelId").asLong();
                ((ObjectNode)newJsonNode).put("requiredCustomerLevelId", requiredCustomerLevelId);
            }
            case "layerInfo" -> {
                LOGGER.info("Rewriting layerInfo to layerInfoId...");
                Long layerInfoId = jsonField.getValue().get("layerInfoId").asLong();
                ((ObjectNode)newJsonNode).put("layerInfoId", layerInfoId);
            }
        }
    }

    private static void rewriteArrayFields(Map.Entry<String, JsonNode> jsonField, JsonNode newJsonNode) {
        switch (jsonField.getKey()) {
            case "marketSegments" -> {
                LOGGER.info("Rewriting marketSegments to marketSegmentIds...");
                if (jsonField.getValue() != null) {
                    ArrayNode marketSegmentIds = new ArrayNode(new JsonNodeFactory(true));
                    ((ArrayNode)jsonField.getValue()).elements().forEachRemaining(marketSegment -> {
                        marketSegmentIds.add(marketSegment.get("marketSegmentId").asLong());
                    });
                    ((ObjectNode)newJsonNode).putArray("marketSegmentIds").addAll(marketSegmentIds);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("marketSegmentIds", null);
                }
            }
            case "customerSegments" -> {
                LOGGER.info("Rewriting customerSegments to customerSegmentIds...");
                if (jsonField.getValue() != null) {
                    ArrayNode customerSegmentIds = new ArrayNode(new JsonNodeFactory(true));
                    ((ArrayNode)jsonField.getValue()).elements().forEachRemaining(customerSegment -> {
                        customerSegmentIds.add(customerSegment.get("customerSegmentId").asLong());
                    });
                    ((ObjectNode)newJsonNode).putArray("customerSegmentIds").addAll(customerSegmentIds);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("customerSegmentIds", null);
                }
            }
            case "allowedGboAgeProfiles" -> {
                LOGGER.info("Rewriting allowedGboAgeProfiles to allowedGboAgeProfileIds...");
                if (jsonField.getValue() != null) {
                    ArrayNode allowedGboAgeProfileIds = new ArrayNode(new JsonNodeFactory(true));
                    ((ArrayNode)jsonField.getValue()).elements().forEachRemaining(allowedGboAgeProfile -> {
                        allowedGboAgeProfileIds.add(allowedGboAgeProfile.get("gboAgeProfileId").asLong());
                    });
                    ((ObjectNode)newJsonNode).putArray("allowedGboAgeProfileIds").addAll(allowedGboAgeProfileIds);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("allowedGboAgeProfileIds", null);
                }
            }
            case "tokenTypes" -> {
                LOGGER.info("Rewriting tokenTypes to tokenTypeIds...");
                if (jsonField.getValue() != null) {
                    ArrayNode tokenTypeIds = new ArrayNode(new JsonNodeFactory(true));
                    ((ArrayNode)jsonField.getValue()).elements().forEachRemaining(tokenType -> {
                        tokenTypeIds.add(tokenType.get("tokenTypeId").asLong());
                    });
                    ((ObjectNode)newJsonNode).putArray("tokenTypeIds").addAll(tokenTypeIds);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("tokenTypeIds", null);
                }
            }
            case "mandatoryCustomerDataItems" -> {
                LOGGER.info("Rewriting mandatoryCustomerDataItems to mandatoryCustomerDataItemIds...");
                if (jsonField.getValue() != null) {
                    ArrayNode mandatoryCustomerDataItemIds = new ArrayNode(new JsonNodeFactory(true));
                    ((ArrayNode)jsonField.getValue()).elements().forEachRemaining(mandatoryCustomerDataItem -> {
                        mandatoryCustomerDataItemIds.add(mandatoryCustomerDataItem.get("mandatoryCustomerDataItemId").asLong());
                    });
                    ((ObjectNode)newJsonNode).putArray("mandatoryCustomerDataItemIds").addAll(mandatoryCustomerDataItemIds);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("mandatoryCustomerDataItemIds", null);
                }
            }
            case "requiredGboPersonalAttributes" -> {
                LOGGER.info("Rewriting requiredGboPersonalAttributes to requiredGboPersonalAttributeIds...");
                if (jsonField.getValue() != null) {
                    ArrayNode requiredGboPersonalAttributeIds = new ArrayNode(new JsonNodeFactory(true));
                    ((ArrayNode)jsonField.getValue()).elements().forEachRemaining(requiredGboPersonalAttribute -> {
                        requiredGboPersonalAttributeIds.add(
                                requiredGboPersonalAttribute.get("requiredGboPersonalAttributeId").asLong());
                    });
                    ((ObjectNode)newJsonNode).putArray("requiredGboPersonalAttributeIds").addAll(requiredGboPersonalAttributeIds);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("requiredGboPersonalAttributeIds", null);
                }
            }
            case "serviceOptions" -> {
                LOGGER.info("Rewriting serviceOptions to serviceOptionIds...");
                if (jsonField.getValue() != null) {
                    ArrayNode serviceOptionIds = new ArrayNode(new JsonNodeFactory(true));
                    ((ArrayNode)jsonField.getValue()).elements().forEachRemaining(serviceOption -> {
                        serviceOptionIds.add(serviceOption.get("serviceOptionId").asLong());
                    });
                    ((ObjectNode)newJsonNode).putArray("serviceOptionIds").addAll(serviceOptionIds);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("serviceOptionIds", null);
                }
            }
            case "incompatibleProducts" -> {
                LOGGER.info("Rewriting incompatibleProducts to remove product names...");
                if (jsonField.getValue() != null) {
                    ArrayNode incompatibleProducts = ((ArrayNode)jsonField.getValue()).deepCopy();
                    incompatibleProducts.elements().forEachRemaining(incompatibleProduct -> {
                        ((ObjectNode)incompatibleProduct).remove("productName");
                    });
                    ((ObjectNode)newJsonNode).putArray("incompatibleProducts").addAll(incompatibleProducts);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("incompatibleProducts", null);
                }
            }
            case "requiredProducts" -> {
                LOGGER.info("Rewriting requiredProducts to remove product names...");
                if (jsonField.getValue() != null) {
                    ArrayNode requiredProducts = ((ArrayNode)jsonField.getValue()).deepCopy();
                    requiredProducts.elements().forEachRemaining(requiredProduct -> {
                        ((ObjectNode)requiredProduct).remove("productName");
                    });
                    ((ObjectNode)newJsonNode).putArray("requiredProducts").addAll(requiredProducts);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("requiredProducts", null);
                }
            }
            case "sellingPeriods" -> {
                if (jsonField.getValue() != null) {
                    LOGGER.info("Rewriting salesTouchpoint in sellingPeriods to salesTouchpointId...");
                    ArrayNode sellingPeriods = ((ArrayNode)jsonField.getValue()).deepCopy();
                    sellingPeriods.elements().forEachRemaining(sellingPeriod -> {
                        Long salesTouchpointId = sellingPeriod.get("salesTouchpoint").get("salesTouchpointId").asLong();
                        ((ObjectNode)sellingPeriod).put("salesTouchpointId", salesTouchpointId);
                        ((ObjectNode)sellingPeriod).remove("salesTouchpoint");
                        if (sellingPeriod.get("forbiddenPaymentMethods") != null) {
                            LOGGER.info("Rewriting forbiddenPaymentMethods to forbiddenPaymentMethodIds...");
                            ArrayNode forbiddenPaymentMethodIds = new ArrayNode(new JsonNodeFactory(true));
                            sellingPeriod.get("forbiddenPaymentMethods").elements().forEachRemaining(forbiddenPaymentMethod -> {
                                forbiddenPaymentMethodIds.add(forbiddenPaymentMethod.get("forbiddenPaymentMethodId").asLong());
                            });
                            ((ObjectNode)sellingPeriod).remove("forbiddenPaymentMethods");
                            if (!forbiddenPaymentMethodIds.isEmpty()) {
                                ((ObjectNode)sellingPeriod).putArray("forbiddenPaymentMethodIds").addAll(forbiddenPaymentMethodIds);
                            } else {
                                ((ObjectNode)sellingPeriod).putRawValue("forbiddenPaymentMethodIds", null);
                            }
                        }
                        if (!sellingPeriod.get("sellingPrices").isNull()) {
                            LOGGER.info("Deep-copying sellingPrices...");
                            ArrayNode sellingPrices = ((ArrayNode)sellingPeriod.get("sellingPrices")).deepCopy();
                            ((ObjectNode)sellingPeriod).remove("sellingPrices");
                            if (!sellingPrices.isEmpty()) {
                                ((ObjectNode)sellingPeriod).putArray("sellingPrices").addAll(sellingPrices);
                            } else {
                                ((ObjectNode)sellingPeriod).putRawValue("sellingPrices", null);
                            }
                        }
                    });
                    ((ObjectNode)newJsonNode).putArray("sellingPeriods").addAll(sellingPeriods);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("sellingPeriods", null);
                }
            }
            case "purchasePrices" -> {
                LOGGER.info("Deep-copying purchasePrices...");
                if (!jsonField.getValue().isNull()) {
                    ArrayNode purchasePrices = ((ArrayNode)jsonField.getValue()).deepCopy();
                    ((ObjectNode)newJsonNode).putArray("purchasePrices").addAll(purchasePrices);
                } else {
                    ((ObjectNode)newJsonNode).putRawValue("purchasePrices", null);
                }
            }
        }
    }
}
