# DroneAttrComparatorService.java

    package io.github.js0ny.ilp_coursework.service;

    import static io.github.js0ny.ilp_coursework.util.AttrComparator.isValueMatched;

    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;

    import io.github.js0ny.ilp_coursework.data.external.Drone;
    import io.github.js0ny.ilp_coursework.data.request.AttrQueryRequest;
    import io.github.js0ny.ilp_coursework.util.AttrOperator;

    import org.springframework.stereotype.Service;
    import org.springframework.web.client.RestTemplate;

    import java.net.URI;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;

    @Service
    public class DroneAttrComparatorService {

        private final String baseUrl;
        private final String dronesEndpoint = "drones";
        private final RestTemplate restTemplate = new RestTemplate();

        /** Constructor, handles the base url here. */
        public DroneAttrComparatorService() {
            String baseUrl = System.getenv("ILP_ENDPOINT");
            if (baseUrl == null || baseUrl.isBlank()) {
                this.baseUrl = "https://ilp-rest-2025-bvh6e9hschfagrgy.ukwest-01.azurewebsites.net/";
            } else {
                // Defensive: Add '/' to the end of the URL
                if (!baseUrl.endsWith("/")) {
                    baseUrl += "/";
                }
                this.baseUrl = baseUrl;
            }
        }

        /**
         * Return an array of ids of drones with a given attribute name and value.
         *
         * <p>Associated service method with {@code /queryAsPath/{attrName}/{attrVal}}
         *
         * @param attrName the attribute name to filter on
         * @param attrVal the attribute value to filter on
         * @return array of drone ids matching the attribute name and value
         * @see #dronesWithAttributeCompared(String, String, AttrOperator)
         * @see io.github.js0ny.ilp_coursework.controller.DroneController#getIdByAttrMap
         */
        public List<String> dronesWithAttribute(String attrName, String attrVal) {
            // Call the helper with EQ operator
            return dronesWithAttributeCompared(attrName, attrVal, AttrOperator.EQ);
        }

        /**
         * Return an array of ids of drones which matches all given complex comparing rules
         *
         * @param attrComparators The filter rule with Name, Value and Operator
         * @return array of drone ids that matches all rules
         */
        public List<String> dronesSatisfyingAttributes(AttrQueryRequest[] attrComparators) {
            Set<String> matchingDroneIds = null;
            for (var comparator : attrComparators) {
                String attribute = comparator.attribute();
                String operator = comparator.operator();
                String value = comparator.value();
                AttrOperator op = AttrOperator.fromString(operator);
                List<String> ids = dronesWithAttributeCompared(attribute, value, op);
                if (matchingDroneIds == null) {
                    matchingDroneIds = new HashSet<>(ids);
                } else {
                    matchingDroneIds.retainAll(ids);
                }
            }
            if (matchingDroneIds == null) {
                return new ArrayList<>();
            }
            return matchingDroneIds.stream().toList();
        }

        /**
         * Helper that wraps the dynamic querying with different comparison operators
         *
         * <p>This method act as a concatenation of {@link
         * io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode, String,
         * AttrOperator)}
         *
         * @param attrName the attribute name to filter on
         * @param attrVal the attribute value to filter on
         * @param op the comparison operator
         * @return array of drone ids matching the attribute name and value (filtered by {@code op})
         * @see io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode, String,
         *     AttrOperator)
         */
        private List<String> dronesWithAttributeCompared(
                String attrName, String attrVal, AttrOperator op) {
            URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
            // This is required to make sure the response is valid
            Drone[] drones = restTemplate.getForObject(droneUrl, Drone[].class);

            if (drones == null) {
                return new ArrayList<>();
            }

            // Use Jackson's ObjectMapper to convert DroneDto to JsonNode for dynamic
            // querying
            ObjectMapper mapper = new ObjectMapper();

            return Arrays.stream(drones)
                    .filter(
                            drone -> {
                                JsonNode node = mapper.valueToTree(drone);
                                JsonNode attrNode = node.findValue(attrName);
                                if (attrNode != null) {
                                    // Manually handle different types of JsonNode
                                    return isValueMatched(attrNode, attrVal, op);
                                } else {
                                    return false;
                                }
                            })
                    .map(Drone::id)
                    .collect(Collectors.toList());
        }
    }