package com.realnet.DataLake.Services;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
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.ObjectNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.realnet.DataLake.Entity.BatchData;
import com.realnet.DataLake.Entity.Data_lake;
import com.realnet.DataLake.Repository.BatchDataRepository;
import com.realnet.DataLake.Repository.Data_lakeRepository;
import com.realnet.SureConnect.Entities.Sure_Connect;
import com.realnet.SureConnect.Service.SureService;
import com.realnet.users.entity1.AppUser;
import com.realnet.users.service1.AppUserServiceImpl;
import com.realnet.utils.Port_Constant;

@Service
public class DataLakeActionService {
	@Autowired
	private Data_lakeRepository Repository;

	@Autowired
	private BatchDataRepository batchRepo;
	@Autowired
	private AppUserServiceImpl userService;

	@Autowired
	private SureService sureService;

//	 sql to json
	public Object sqltojson(Integer lakeid) {
		Data_lake lake = Repository.findById(lakeid).get();
		String datalake_type = lake.getDatalake_type();

		Object responseBody = null; // 👈 this is what we’ll return

		if (datalake_type.equalsIgnoreCase("blending") && lake.getBlending_lakeids() != null
				&& lake.getSqlquery_json() != null) {

			String blending_lakeids = lake.getBlending_lakeids(); // e.g. "12,13"
			String sqlquery_json = lake.getSqlquery_json(); // e.g. "select * from gtest, gtest2"

			// Step 1️⃣: Collect all child lakes
			String[] lakeIds = blending_lakeids.split(",");
			Map<String, JsonNode> lakeDataMap = new HashMap<>();
			ObjectMapper mapper = new ObjectMapper();

			for (String idStr : lakeIds) {
				int id = Integer.parseInt(idStr.trim());
				Data_lake child = Repository.findById(id).orElse(null);
				if (child == null)
					continue;

				String url = child.getUrl_endpoint();

//				ResponseEntity<String> response = GETWithString(url, child.getSure_connect_id());
				ResponseEntity<Object> response = GETWithObject(url, child.getSure_connect_id());

				Object body = response.getBody();

				try {

					String rawBody = mapper.writeValueAsString(body);
					JsonNode data = mapper.readTree(rawBody);
					// Normalize to array
					if (!data.isArray()) {
						ArrayNode arr = mapper.createArrayNode();
						arr.add(data);
						data = arr;
					}
					lakeDataMap.put(child.getName().toLowerCase(), data);
					System.out.println("Loaded data for: " + child.getName());
				} catch (Exception e) {
					System.err.println("Error parsing data from " + child.getName() + ": " + e.getMessage());
				}
			}

			// Step 2️⃣: Parse SQL for table names
			List<String> tables = extractTableNames(sqlquery_json);
			System.out.println("Tables in query: " + tables);

			// Step 3️⃣: Perform JOIN or UNION based on query type
			ArrayNode result = mapper.createArrayNode();

			if (sqlquery_json.toLowerCase().contains("join") || sqlquery_json.toLowerCase().contains("where")) {
				// Perform naive JOIN on common field names
				result = performJoin(mapper, lakeDataMap, tables);
			} else {
				// Perform simple UNION of all tables (cross merge)
				result = performUnion(mapper, lakeDataMap, tables);
			}

			System.out.println("✅ Final blended JSON:");
			System.out.println(result.toPrettyString());
			// Step 4️⃣: Convert ArrayNode → Object for return
			try {
				responseBody = mapper.readValue(mapper.writeValueAsString(result), Object.class);
			} catch (Exception e) {
				throw new RuntimeException("Error converting result to Object: " + e.getMessage());
			}
		}

		return responseBody;

	}

	/** Extract table names from SQL query like "select * from gtest, gtest2" */
	private List<String> extractTableNames(String sql) {
		List<String> tables = new ArrayList<>();
		String lower = sql.toLowerCase();
		String regex = "from\\s+([a-zA-Z0-9_,\\s]+)";
		Matcher m = Pattern.compile(regex).matcher(lower);
		if (m.find()) {
			String group = m.group(1);
			for (String name : group.split(",")) {
				tables.add(name.trim());
			}
		}
		return tables;
	}

	/** Simple union of all tables */
	private ArrayNode performUnion(ObjectMapper mapper, Map<String, JsonNode> lakeDataMap, List<String> tables) {
		ArrayNode result = mapper.createArrayNode();

		for (String table : tables) {
			JsonNode data = lakeDataMap.get(table.toLowerCase());
			if (data == null)
				continue;

			for (JsonNode row : data) {
				ObjectNode newRow = mapper.createObjectNode();
				Iterator<String> fields = row.fieldNames();
				while (fields.hasNext()) {
					String field = fields.next();
					newRow.put(table + "." + field, row.get(field).asText());
				}
				result.add(newRow);
			}
		}
		return result;
	}

	/** Simple JOIN based on common keys */
	private ArrayNode performJoin(ObjectMapper mapper, Map<String, JsonNode> lakeDataMap, List<String> tables) {
		ArrayNode result = mapper.createArrayNode();
		if (tables.size() < 2)
			return result;

		JsonNode first = lakeDataMap.get(tables.get(0).toLowerCase());
		JsonNode second = lakeDataMap.get(tables.get(1).toLowerCase());

		if (first == null || second == null)
			return result;

		// Find common key (like "id" or "emp_id")
		String joinKey = findCommonKey(first, second);

		for (JsonNode row1 : first) {
			for (JsonNode row2 : second) {
				if (joinKey != null && row1.has(joinKey) && row2.has(joinKey)
						&& row1.get(joinKey).asText().equals(row2.get(joinKey).asText())) {

					ObjectNode merged = mapper.createObjectNode();
					row1.fieldNames().forEachRemaining(f -> merged.put(tables.get(0) + "." + f, row1.get(f).asText()));
					row2.fieldNames().forEachRemaining(f -> merged.put(tables.get(1) + "." + f, row2.get(f).asText()));
					result.add(merged);
				}
			}
		}
		return result;
	}

	/** Finds a likely common join key between two datasets */
	private String findCommonKey(JsonNode a, JsonNode b) {
		Set<String> aKeys = new HashSet<>();
		a.get(0).fieldNames().forEachRemaining(aKeys::add);

		Set<String> bKeys = new HashSet<>();
		b.get(0).fieldNames().forEachRemaining(bKeys::add);

		for (String k : aKeys) {
			if (bKeys.contains(k))
				return k;
		}
		return null;
	}

//	 get all keys of url endpoint
	public Map<String, Object> getAllKeys(Integer lakeid) throws JsonProcessingException {
		Data_lake old = Repository.findById(lakeid).get();
		String url = old.getUrl_endpoint();

		ResponseEntity<String> response = GETMethodAsString(url, old.getSure_connect_id());

		String rawBody = response.getBody();

		ObjectMapper jsonMapper = new ObjectMapper();
		Object responseBody;

		// Detect and convert if XML
		if (isXml(rawBody)) {
			try {
				XmlMapper xmlMapper = new XmlMapper();
				JsonNode xmlNode = xmlMapper.readTree(rawBody.getBytes());
				String jsonFromXml = jsonMapper.writeValueAsString(xmlNode);
				responseBody = jsonMapper.readValue(jsonFromXml, Object.class);
				System.out.println("XML detected and converted to JSON successfully.");
			} catch (Exception e) {
				throw new RuntimeException("Failed to convert XML to JSON: " + e.getMessage());
			}
		} else {
			try {
				responseBody = jsonMapper.readValue(rawBody, Object.class);
				System.out.println("JSON response detected.");
			} catch (Exception e) {
				throw new RuntimeException("Invalid JSON format: " + e.getMessage());
			}
		}

		Map<String, Object> result = new HashMap<>();
		extractTableAndHeaders(result, responseBody);

		// Add table name from database
		result.put("tableName", old.getName());

		System.out.println("Keys extracted successfully.");
		return result;
	}

	public static Map<String, Object> extractTableAndHeaders(Map<String, Object> result, Object responseBody) {
		ObjectMapper mapper = new ObjectMapper();

		try {
			JsonNode root = mapper.valueToTree(responseBody);
			JsonNode firstNode = root.isArray() && root.size() > 0 ? root.get(0) : root;

			Set<String> headers = new LinkedHashSet<>();
			Iterator<String> fieldNames = firstNode.fieldNames();
			while (fieldNames.hasNext()) {
				String field = fieldNames.next();
				if (!field.equalsIgnoreCase("tableName")) {
					headers.add(field);
				}
			}

			result.put("headers", headers);
		} catch (Exception e) {
			result.put("error", "Invalid JSON: " + e.getMessage());
		}

		return result;
	}

//	apply calculated json
	public Data_lake applyCalculation(Integer id) throws JsonProcessingException {

		ObjectMapper mapper = new ObjectMapper();

		Data_lake old = Repository.findById(id).get();
		String datalake_type = old.getDatalake_type();
		if (datalake_type != null && datalake_type.equalsIgnoreCase("blending") && old.getBlending_lakeids() != null
				&& old.getSqlquery_json() != null) {

			Object sqltojson = sqltojson(id);

			String jsonString = mapper.writeValueAsString(sqltojson);
			old.setJson(jsonString);

			// Process and insert into BatchData as before

			// Process and insert into BatchData
			processBatchData(old, sqltojson, mapper);

			String Url = Port_Constant.LOCALHOST + ":9292" + "/Data_lake/Data_lake/merge/" + old.getId();
			old.setUrl_endpoint(Url);

			old.setUpdatedBy(getUser().getUserId());

			final Data_lake saved = Repository.save(old);
			return saved;
		}

		String url = old.getUrl();

		ResponseEntity<Object> response = GETWithObject(url, old.getSure_connect_id());
		Object data = response.getBody();

		String rawBody = mapper.writeValueAsString(data);
		try {

			List<Data_lake> datlakes = Repository.findAllByRefDatlakeId(getUser().getUserId(), id);
			if (datlakes != null) {

				datlakes.forEach(lake -> {
					try {
						Updatejson(lake.getId(), rawBody);
					} catch (JsonProcessingException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				});
			}

		} catch (Exception e2) {
			System.out.println(" exception " + e2);
			// TODO: handle exception
		}
		Data_lake saved = Updatejson(id, rawBody);

		System.out.println(" json updated..");
		return saved;

	}

	public Data_lake Updatejson(Integer id, String rawBody) throws JsonProcessingException {

		Data_lake lake = Repository.findById(id).get();

//		String url = old.getUrl();
//
//		ResponseEntity<String> response = GETAsString(url);
//		String rawBody = response.getBody();

		// Convert the JSON object (ArrayList, Map, etc.) to a String
//		Object responseBody = response.getBody();
		ObjectMapper mapper = new ObjectMapper();

//		String jsonString = mapper.writeValueAsString(rawBody);
		ObjectMapper jsonMapper = new ObjectMapper();
		Object responseBody;

		// Detect and convert if XML
		if (isXml(rawBody)) {
			try {
				// Convert XML → JSON tree

				XmlMapper xmlMapper = new XmlMapper();

				JsonNode xmlNode = xmlMapper.readTree(rawBody.getBytes());
				// Convert to standard JSON structure
				String jsonFromXml = jsonMapper.writeValueAsString(xmlNode);
				responseBody = jsonMapper.readValue(jsonFromXml, Object.class);
				System.out.println("XML detected and converted to JSON successfully.");
			} catch (Exception e) {
				throw new RuntimeException("Failed to convert XML to JSON: " + e.getMessage());
			}
		} else {
			// Normal JSON response
			try {
				responseBody = jsonMapper.readValue(rawBody, Object.class);
				System.out.println("JSON response detected.");
			} catch (Exception e) {
				throw new RuntimeException("Invalid JSON format: " + e.getMessage());
			}
		}

		// Step 2️⃣: Apply mapping if available
		String mapping_json = lake.getMapping_json();
		if (mapping_json != null && !mapping_json.trim().isEmpty()) {
			try {
				Map<String, String> keyMapping = mapper.readValue(mapping_json,
						new TypeReference<Map<String, String>>() {
						});
				responseBody = applyFieldMapping(responseBody, keyMapping);
				System.out.println("✅ Applied header mapping successfully.");
			} catch (Exception e) {
				throw new RuntimeException("Failed to apply mapping JSON: " + e.getMessage());
			}
		}
		// ✅ Handle calculated fields before batch processing
		if (Boolean.TRUE.equals(lake.getIscalculatedfield()) && lake.getCalculated_field_json() != null) {
			try {
				responseBody = applyCalculatedFields(responseBody, lake.getCalculated_field_json(), mapper, lake);
				System.out.println("Calculated fields applied successfully.");
			} catch (Exception e) {
				System.err.println("Failed to process calculated fields: " + e.getMessage());
			}
		}

		String jsonString = mapper.writeValueAsString(responseBody);
		lake.setJson(jsonString);

		// Process and insert into BatchData as before

		// Process and insert into BatchData
		processBatchData(lake, responseBody, mapper);

		String Url = Port_Constant.LOCALHOST + ":9292" + "/Data_lake/Data_lake/merge/" + lake.getId();
		lake.setUrl_endpoint(Url);

		lake.setUpdatedBy(getUser().getUserId());

		final Data_lake saved = Repository.save(lake);

		System.out.println(" json updated..");
		return saved;

	}

	private Object applyFieldMapping(Object input, Map<String, String> keyMapping) {
		ObjectMapper mapper = new ObjectMapper();

		if (input instanceof List) {
			ArrayNode newArray = mapper.createArrayNode();
			for (Object obj : (List<?>) input) {
				JsonNode newNode = renameFields(mapper.valueToTree(obj), keyMapping, mapper);
				newArray.add(newNode);
			}
			return newArray;
		} else {
			return renameFields(mapper.valueToTree(input), keyMapping, mapper);
		}
	}

	private JsonNode renameFields(JsonNode node, Map<String, String> keyMapping, ObjectMapper mapper) {
		ObjectNode updated = mapper.createObjectNode();

		node.fieldNames().forEachRemaining(key -> {
			JsonNode value = node.get(key);
			String newKey = keyMapping.getOrDefault(key, key);
			updated.set(newKey, value);
		});

		return updated;
	}

	@SuppressWarnings("unchecked")
	private Object applyCalculatedFields(Object responseBody, String calculatedFieldJson, ObjectMapper mapper,
			Data_lake old) throws JsonProcessingException {

		// Parse the calculated field JSON
		List<Map<String, Object>> calcFields = mapper.readValue(calculatedFieldJson, List.class);

//		if (!(responseBody instanceof List)) {
//			responseBody = Arrays.asList(responseBody);
//		}

		// ✅ Case 1: Already a Java List
		if (responseBody instanceof List) {
			// Do nothing
		}

		// ✅ Case 2: Jackson ArrayNode (convert to List)
		else if (responseBody instanceof com.fasterxml.jackson.databind.node.ArrayNode) {
			responseBody = mapper.convertValue(responseBody, List.class);
		}

		// ✅ Case 3: Jackson ObjectNode (convert to single object inside list)
		else if (responseBody instanceof com.fasterxml.jackson.databind.node.ObjectNode) {
			Map<String, Object> obj = mapper.convertValue(responseBody, Map.class);
			responseBody = Collections.singletonList(obj);
		}

		// ✅ Case 4: JSON String input
		else if (responseBody instanceof String) {
			String json = ((String) responseBody).trim();
			if (json.startsWith("[")) {
				responseBody = mapper.readValue(json, List.class);
			} else if (json.startsWith("{")) {
				Map<String, Object> singleObject = mapper.readValue(json, Map.class);
				responseBody = Collections.singletonList(singleObject);
			} else {
				throw new IllegalArgumentException("Invalid JSON format: " + json);
			}
		}

		// ✅ Case 5: Single map
		else if (responseBody instanceof Map) {
			responseBody = Collections.singletonList(responseBody);
		}

		// ❌ Anything else — throw error
		else {
			throw new IllegalArgumentException("Unexpected response body type: " + responseBody.getClass());
		}
		List<Map<String, Object>> records = (List<Map<String, Object>>) responseBody;

		for (Map<String, Object> calc : calcFields) {
			String type = (String) calc.get("type");
			if ("groupby".equalsIgnoreCase(type)) {
				// ✅ Handle group-by aggregation
				List<Map<String, Object>> groupbyrecords = applyGroupBy(records, calc);
				String groupByRecord = mapper.writeValueAsString(groupbyrecords);

				old.setGroupby_json(groupByRecord);

			} else {
				// ✅ Handle calculated / complex operations (as before)
				for (Map<String, Object> record : records) {
					String fieldName = (String) calc.get("fieldName");
					String operation = (String) calc.get("operation");
					List<Map<String, Object>> components = (List<Map<String, Object>>) calc.get("fieldComponents");

					// Handle constant fields
					if (components != null) {
						for (Map<String, Object> comp : components) {
							String subField = (String) comp.get("field");
							Boolean isConstant = comp.get("isConstant") != null && (Boolean) comp.get("isConstant");
							Object constantValue = comp.get("constant");
							if (isConstant && subField != null) {
								record.put(subField, parseNumberOrString(constantValue));
							}
						}
					}

					// Handle complex expression
					if ("complex".equalsIgnoreCase(operation) && calc.get("complexEquation") != null) {
						String equation = (String) calc.get("complexEquation");
						Object result = evaluateComplexExpression(equation, record);
						record.put(fieldName, result);
						continue;
					}

					// Handle normal arithmetic
					List<Object> values = new ArrayList<>();
					if (components != null) {
						for (Map<String, Object> comp : components) {
							Boolean isConstant = comp.get("isConstant") != null && (Boolean) comp.get("isConstant");
							Object val = isConstant ? comp.get("constant") : record.get(comp.get("field"));
							if (val != null) {
								values.add(val);
							}
						}
					}

					Object result = performOperation(values, operation);
					record.put(fieldName, result);
				}
			}
		}

		return records;
	}

	@SuppressWarnings("unchecked")
	private List<Map<String, Object>> applyGroupBy(List<Map<String, Object>> records, Map<String, Object> groupConfig) {
		List<String> groupFields = (List<String>) groupConfig.get("groupFields");
		List<Map<String, Object>> aggregations = (List<Map<String, Object>>) groupConfig.get("aggregations");

		// Group records by key
		Map<String, List<Map<String, Object>>> grouped = new LinkedHashMap<>();
		for (Map<String, Object> record : records) {
			String key = groupFields.stream().map(f -> String.valueOf(record.getOrDefault(f, "")))
					.collect(Collectors.joining("|"));
			grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(record);
		}

		// Aggregate results per group
		List<Map<String, Object>> result = new ArrayList<>();

		for (Map.Entry<String, List<Map<String, Object>>> entry : grouped.entrySet()) {
			Map<String, Object> groupRecord = new LinkedHashMap<>();

			// Add group fields
			String[] parts = entry.getKey().split("\\|");
			for (int i = 0; i < groupFields.size(); i++) {
				groupRecord.put(groupFields.get(i), parts[i]);
			}

			List<Map<String, Object>> groupRows = entry.getValue();

			// Apply each aggregation
			for (Map<String, Object> agg : aggregations) {
				String field = (String) agg.get("field");
				String operation = ((String) agg.get("operation")).toLowerCase();
				List<Double> numericValues = groupRows.stream().map(r -> toDouble(r.get(field)))
						.filter(v -> !Double.isNaN(v)).collect(Collectors.toList());

				Object aggResult;
				switch (operation) {
				case "count":
					aggResult = groupRows.size();
					break;
				case "sum":
					aggResult = numericValues.stream().mapToDouble(Double::doubleValue).sum();
					break;
				case "avg":
					aggResult = numericValues.isEmpty() ? 0.0
							: numericValues.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
					break;
				case "min":
					aggResult = numericValues.isEmpty() ? null
							: numericValues.stream().mapToDouble(Double::doubleValue).min().orElse(0.0);
					break;
				case "max":
					aggResult = numericValues.isEmpty() ? null
							: numericValues.stream().mapToDouble(Double::doubleValue).max().orElse(0.0);
					break;
				case "median":
					aggResult = calculateMedian(numericValues);
					break;
				case "mode":
					aggResult = calculateMode(numericValues);
					break;
				case "stdev":
					aggResult = calculateStdDev(numericValues);
					break;
				default:
					aggResult = null;
				}

				groupRecord.put(field + "_" + operation, aggResult);
			}

			result.add(groupRecord);
		}

		return result;
	}

	// --- Statistical helpers ---

	private Double calculateMedian(List<Double> values) {
		if (values == null || values.isEmpty())
			return null;
		List<Double> sorted = new ArrayList<>(values);
		Collections.sort(sorted);
		int n = sorted.size();
		if (n % 2 == 1) {
			return sorted.get(n / 2);
		} else {
			return (sorted.get(n / 2 - 1) + sorted.get(n / 2)) / 2.0;
		}
	}

	private Double calculateMode(List<Double> values) {
		if (values == null || values.isEmpty())
			return null;
		Map<Double, Long> freq = values.stream().collect(Collectors.groupingBy(v -> v, Collectors.counting()));
		return freq.entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse(null);
	}

	private Double calculateStdDev(List<Double> values) {
		if (values == null || values.isEmpty())
			return null;
		double mean = values.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
		double variance = values.stream().mapToDouble(v -> Math.pow(v - mean, 2)).average().orElse(0.0);
		return Math.sqrt(variance);
	}

	private Object evaluateComplexExpression(String expression, Map<String, Object> record) {
		try {
			// Replace field names with values dynamically
			for (Map.Entry<String, Object> e : record.entrySet()) {
				String field = e.getKey();
				Object val = e.getValue();
				expression = expression.replaceAll("\\b" + field + "\\b", String.valueOf(val != null ? val : 0));
			}

			// Evaluate using ScriptEngine (works on Java 1.8)
			javax.script.ScriptEngine engine = new javax.script.ScriptEngineManager().getEngineByName("JavaScript");
			Object result = engine.eval(expression);
			return result;
		} catch (Exception e) {
			System.err.println("Complex expression error: " + e.getMessage());
			return null;
		}
	}

//	perform normal operation
	private Object performOperation(List<Object> values, String operation) {
		if (values.isEmpty())
			return null;

		switch (operation.toLowerCase()) {
		case "add":
			double sum = 0;
			for (Object v : values)
				sum += toDouble(v);
			return sum;

		case "subtract":
			double result = toDouble(values.get(0));
			for (int i = 1; i < values.size(); i++)
				result -= toDouble(values.get(i));
			return result;

		case "multiply":
			double prod = 1;
			for (Object v : values)
				prod *= toDouble(v);
			return prod;

		case "divide":
			double div = toDouble(values.get(0));
			for (int i = 1; i < values.size(); i++) {
				double val = toDouble(values.get(i));
				if (val != 0)
					div /= val;
			}
			return div;

		case "percentage":
			if (values.size() < 2)
				return null;
			double num = toDouble(values.get(0));
			double den = toDouble(values.get(1));
			return den == 0 ? null : (num * den) / 100;

		case "concat":
			return values.stream().map(Object::toString).collect(Collectors.joining("_"));

		default:
			return null;
		}
	}

	/**
	 * ✅ Evaluates complex equations using JavaScript engine Supports math
	 * operations, parentheses, and string concatenation.
	 */
	private Object evaluateComplexEquation(String expression, Map<String, Object> record,
			List<Map<String, Object>> fieldComponents) {

		if (expression == null || expression.trim().isEmpty())
			return null;

		ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");

		try {
			// 🔹 Replace constants
			if (fieldComponents != null) {
				for (Map<String, Object> comp : fieldComponents) {
					String field = (String) comp.get("field");
					Object constant = comp.get("constant");
					Boolean isConstant = comp.get("isConstant") != null && (Boolean) comp.get("isConstant");

					if (isConstant && constant != null) {
						String constVal = constant.toString();
						expression = expression.replaceAll("\\b" + field + "\\b", constVal);
					}
				}
			}

			// 🔹 Replace field values from record
			for (Map.Entry<String, Object> entry : record.entrySet()) {
				String key = entry.getKey();
				Object val = entry.getValue();
				if (val != null) {
					String safeValue = val.toString();

					// If it's a string containing letters, wrap it in quotes for JS
					if (!safeValue.matches("^-?\\d+(\\.\\d+)?$")) {
						safeValue = "'" + safeValue.replace("'", "\\'") + "'";
					}

					expression = expression.replaceAll("\\b" + key + "\\b", safeValue);
				}
			}

			// Evaluate the expression safely
			Object result = engine.eval(expression);
			System.out.println(" exoression is : " + expression + "  and " + result);
			return result;

		} catch (Exception e) {
			System.err.println("❌ Error evaluating complex equation: " + e.getMessage());
			return null;
		}
	}

	private double toDouble(Object val) {
		if (val == null)
			return 0.0;
		try {
			return Double.parseDouble(val.toString().trim());
		} catch (NumberFormatException e) {
			return 0.0;
		}
	}

	private Object parseNumberOrString(Object val) {
		if (val == null)
			return null;
		String str = val.toString().trim();
		try {
			return Double.parseDouble(str);
		} catch (NumberFormatException e) {
			return str;
		}
	}

	private boolean isXml(String content) {
		if (content == null)
			return false;
		String trimmed = content.trim();
		// XML usually starts with '<' and ends with '>'
		return trimmed.startsWith("<") && trimmed.endsWith(">");
	}

	private void processBatchData(Data_lake dataLake, Object responseBody, ObjectMapper mapper)
			throws JsonProcessingException {

		int batchVolume = (dataLake.getBatch_volume() != null && dataLake.getBatch_volume() > 0)
				? dataLake.getBatch_volume()
				: 100; // default batch size if not given

		// Convert to JsonNode
		JsonNode jsonNode = mapper.valueToTree(responseBody);

		if (jsonNode.isArray()) {
			ArrayNode arrayNode = (ArrayNode) jsonNode;
			int total = arrayNode.size();
			System.out.println("Total records: " + total);

			for (int i = 0; i < total; i += batchVolume) {
				int end = Math.min(i + batchVolume, total);

				// Create a sub-array manually
				ArrayNode subArray = mapper.createArrayNode();
				for (int j = i; j < end; j++) {
					subArray.add(arrayNode.get(j));
				}

				String subJson = mapper.writeValueAsString(subArray);

				BatchData batch = new BatchData();
				batch.setDatalake_id(dataLake.getId());
				batch.setBatchjson(subJson);
				batchRepo.save(batch);
			}

			System.out.println("Inserted " + (int) Math.ceil((double) total / batchVolume) + " batch records.");

		} else {
			// Single object → one record
			BatchData batch = new BatchData();
			batch.setDatalake_id(dataLake.getId());
			batch.setBatchjson(mapper.writeValueAsString(jsonNode));
			batchRepo.save(batch);

			System.out.println("Inserted single batch record.");
		}
	}

	public String mergeBatchData(Integer datalakeId) throws Exception {
		List<BatchData> batchList = batchRepo.findByDatalake_id(datalakeId);

		if (batchList.isEmpty()) {
			throw new RuntimeException("No batch data found for datalake_id: " + datalakeId);
		}

		ObjectMapper mapper = new ObjectMapper();
		ArrayNode mergedArray = mapper.createArrayNode();

		for (BatchData batch : batchList) {
			String json = batch.getBatchjson();
			JsonNode node = mapper.readTree(json);

			if (node.isArray()) {
				// Add each element to merged array
				for (JsonNode item : node) {
					mergedArray.add(item);
				}
			} else {
				// Single object, just add directly
				mergedArray.add(node);
			}
		}

		String mergedJson = mapper.writeValueAsString(mergedArray);
		System.out.println("Merged JSON size: " + mergedArray.size());
		return mergedJson;
	}

	public ResponseEntity<Object> GET(String get) {
		RestTemplate restTemplate = new RestTemplate();

		ResponseEntity<Object> u = restTemplate.getForEntity(get, Object.class);

		return u;

	}

	public ResponseEntity<String> GETAsString1(String url) {
		RestTemplate restTemplate = new RestTemplate();
		return restTemplate.getForEntity(url, String.class);
	}

	public AppUser getUser() {
		AppUser user = userService.getLoggedInUser();
		return user;

	}

	public ResponseEntity<String> GETMethodAsString(String url, Integer sureid) {
		RestTemplate restTemplate = new RestTemplate();

		HttpHeaders headers = new HttpHeaders();
		headers.setAccept(Collections.singletonList(MediaType.ALL)); // accept JSON or XML
		headers.setContentType(MediaType.APPLICATION_JSON);

		// 🔹 Add your token here (you can make it dynamic)
		String token = getToken(sureid); // helper method (see below)
		if (token != null && !token.isEmpty()) {
			headers.set("Authorization", "Bearer " + token);
		}

		HttpEntity<String> entity = new HttpEntity<>(headers);

		ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);

		return response;
	}

	public ResponseEntity<Object> GETWithObject(String url, Integer sureid) {
		RestTemplate restTemplate = new RestTemplate();

		HttpHeaders headers = new HttpHeaders();
		headers.setAccept(Collections.singletonList(MediaType.ALL)); // accept JSON or XML
		headers.setContentType(MediaType.APPLICATION_JSON);

		// 🔹 Add your token here (you can make it dynamic)
		String token = getToken(sureid); // helper method (see below)
		if (token != null && !token.isEmpty()) {
			headers.set("Authorization", "Bearer " + token);
		}

		HttpEntity<String> entity = new HttpEntity<>(headers);

		ResponseEntity<Object> response = restTemplate.exchange(url, HttpMethod.GET, entity, Object.class);

		return response;
	}

	private String getToken(Integer sureid) {
		Sure_Connect connect = sureService.getbyid(sureid);
		String access_token = connect.getAccess_token();

		return access_token; // optional
	}

}
