/*
 * Copyright 2024-2026 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.cloud.ai.graph.store.stores;

import com.alibaba.cloud.ai.graph.store.NamespaceListRequest;
import com.alibaba.cloud.ai.graph.store.Store;
import com.alibaba.cloud.ai.graph.store.StoreItem;
import com.alibaba.cloud.ai.graph.store.StoreSearchRequest;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Base64;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Abstract base class for Store implementations providing common validation and utility
 * methods.
 * <p>
 * This class offers a foundation for implementing the Store interface with consistent
 * validation behavior and common helper methods.
 * </p>
 *
 * @author Spring AI Alibaba
 * @since 1.0.0.3
 */
public abstract class BaseStore implements Store {

	/**
	 * Validates the putItem parameters.
	 * @param item the item to validate
	 */
	protected void validatePutItem(StoreItem item) {
		if (item == null) {
			throw new IllegalArgumentException("item cannot be null");
		}
		if (item.getNamespace() == null) {
			throw new IllegalArgumentException("namespace cannot be null");
		}
		if (item.getKey() == null || item.getKey().trim().isEmpty()) {
			throw new IllegalArgumentException("key cannot be null or empty");
		}
	}

	/**
	 * Validates the getItem parameters.
	 * @param namespace namespace
	 * @param key key
	 */
	protected void validateGetItem(List<String> namespace, String key) {
		if (namespace == null) {
			throw new IllegalArgumentException("namespace cannot be null");
		}
		if (key == null) {
			throw new IllegalArgumentException("key cannot be null");
		}
		if (key.trim().isEmpty()) {
			throw new IllegalArgumentException("key cannot be empty");
		}
	}

	/**
	 * Validates the deleteItem parameters.
	 * @param namespace namespace
	 * @param key key
	 */
	protected void validateDeleteItem(List<String> namespace, String key) {
		if (namespace == null) {
			throw new IllegalArgumentException("namespace cannot be null");
		}
		if (key == null) {
			throw new IllegalArgumentException("key cannot be null");
		}
		if (key.trim().isEmpty()) {
			throw new IllegalArgumentException("key cannot be empty");
		}
	}

	/**
	 * Validates the searchItems parameters.
	 * @param searchRequest search request
	 */
	protected void validateSearchItems(StoreSearchRequest searchRequest) {
		if (searchRequest == null) {
			throw new IllegalArgumentException("searchRequest cannot be null");
		}
	}

	/**
	 * Validates the listNamespaces parameters.
	 * @param namespaceRequest namespace request
	 */
	protected void validateListNamespaces(NamespaceListRequest namespaceRequest) {
		if (namespaceRequest == null) {
			throw new IllegalArgumentException("namespaceRequest cannot be null");
		}
	}

	/**
	 * Create store key from namespace and key Uses safe encoding to avoid conflicts from
	 * special characters.
	 * @param namespace namespace
	 * @param key key
	 * @return store key
	 */
	protected String createStoreKey(List<String> namespace, String key) {
		try {
			Map<String, Object> keyData = new HashMap<>();
			keyData.put("namespace", namespace);
			keyData.put("key", key);
			return Base64.getEncoder().encodeToString(new ObjectMapper().writeValueAsBytes(keyData));
		}
		catch (Exception e) {
			throw new RuntimeException("Failed to create store key", e);
		}
	}

	/**
	 * Parse store key to namespace and key.
	 * @param storeKey store key
	 * @return array containing [namespace, key]
	 */
	@SuppressWarnings("unchecked")
	protected Object[] parseStoreKey(String storeKey) {
		try {
			byte[] decoded = Base64.getDecoder().decode(storeKey);
			Map<String, Object> keyData = new ObjectMapper().readValue(decoded, Map.class);
			List<String> namespace = (List<String>) keyData.get("namespace");
			String key = (String) keyData.get("key");
			return new Object[] { namespace, key };
		}
		catch (Exception e) {
			throw new RuntimeException("Failed to parse store key: " + storeKey, e);
		}
	}

	/**
	 * Check if namespace starts with given prefix.
	 * @param namespace namespace to check
	 * @param prefix prefix to match
	 * @return true if starts with prefix
	 */
	protected boolean startsWithPrefix(List<String> namespace, List<String> prefix) {
		if (prefix.isEmpty()) {
			return true;
		}
		if (prefix.size() > namespace.size()) {
			return false;
		}
		for (int i = 0; i < prefix.size(); i++) {
			if (!Objects.equals(namespace.get(i), prefix.get(i))) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Check if item matches the search filter.
	 * @param item item to check
	 * @param searchRequest search parameters
	 * @return true if matches
	 */
	protected boolean matchesSearchCriteria(StoreItem item, StoreSearchRequest searchRequest) {
		// Namespace filter
		List<String> namespaceFilter = searchRequest.getNamespace();
		if (!namespaceFilter.isEmpty() && !startsWithPrefix(item.getNamespace(), namespaceFilter)) {
			return false;
		}

		// Text query filter
		String query = searchRequest.getQuery();
		if (query != null && !query.trim().isEmpty()) {
			String lowerQuery = query.toLowerCase();
			// Search in key
			if (!item.getKey().toLowerCase().contains(lowerQuery)) {
				// Search in value
				String valueStr = item.getValue().toString().toLowerCase();
				if (!valueStr.contains(lowerQuery)) {
					return false;
				}
			}
		}

		// Custom filters
		Map<String, Object> filters = searchRequest.getFilter();
		if (!filters.isEmpty()) {
			Map<String, Object> itemValue = item.getValue();
			for (Map.Entry<String, Object> filter : filters.entrySet()) {
				Object itemVal = itemValue.get(filter.getKey());
				if (!Objects.equals(itemVal, filter.getValue())) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * Create comparator for sorting items.
	 * @param searchRequest search parameters
	 * @return comparator
	 */
	protected Comparator<StoreItem> createComparator(StoreSearchRequest searchRequest) {
		List<String> sortFields = searchRequest.getSortFields();
		boolean ascending = searchRequest.isAscending();

		return (item1, item2) -> {
			for (String field : sortFields) {
				int comparison = compareByField(item1, item2, field);
				if (comparison != 0) {
					return ascending ? comparison : -comparison;
				}
			}
			return 0;
		};
	}

	/**
	 * Compare two items by field.
	 * @param item1 first item
	 * @param item2 second item
	 * @param field field name
	 * @return comparison result
	 */
	@SuppressWarnings("unchecked")
	private int compareByField(StoreItem item1, StoreItem item2, String field) {
		switch (field) {
			case "createdAt":
				return Long.compare(item1.getCreatedAt(), item2.getCreatedAt());
			case "updatedAt":
				return Long.compare(item1.getUpdatedAt(), item2.getUpdatedAt());
			case "key":
				return item1.getKey().compareTo(item2.getKey());
			case "namespace":
				return String.join("/", item1.getNamespace()).compareTo(String.join("/", item2.getNamespace()));
			default:
				// Try to compare by value field
				Object val1 = item1.getValue().get(field);
				Object val2 = item2.getValue().get(field);
				if (val1 == null && val2 == null) {
					return 0;
				}
				if (val1 == null) {
					return -1;
				}
				if (val2 == null) {
					return 1;
				}
				if (val1 instanceof Comparable && val2 instanceof Comparable) {
					return ((Comparable<Object>) val1).compareTo(val2);
				}
				return val1.toString().compareTo(val2.toString());
		}
	}

}
