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());
    }
}
