package org.micord.service;

import org.micord.config.DatabaseConnection;
import org.micord.enums.ConfigType;
import org.micord.exceptions.IllegalRequestParametersException;
import org.micord.exceptions.ValidationException;
import org.micord.models.requests.BaseRequest;
import org.micord.models.requests.RequestParameters;
import org.micord.models.requests.RequestValidationRules;
import org.micord.models.requests.downloads.BaseDownloadRequest;
import org.micord.models.validations.ValidationRule;
import org.micord.models.validations.ValidationRules;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
public class ValidationService {

	public static final String IS_EMPTY_DATES_ALLOWED = "isEmptyDatesAllowed";
	public static final String IS_EMPTY_IDS_ALLOWED = "isEmptyIdsAllowed";

	@Autowired
	private ConfigService configService;

	public Map<String, Map<String, Object>> validate(List<String> ids, ValidationRule rule) throws SQLException {

		String query = rule.getRequestURL();
		if (!query.contains("${endpointArguments}")) {
			throw new IllegalArgumentException("The query must contain the placeholder '${endpointArguments}' for ID replacement.");
		}

		String finalQuery = query.replace("${endpointArguments}", "(" + ids.stream().map(id -> "?").collect(Collectors.joining(", ")) + ")");

		try (Connection connection = DatabaseConnection.getConnection(
			rule.getSqlConnectionParams());
			 PreparedStatement preparedStatement = connection.prepareStatement(finalQuery)) {

			for (int i = 0; i < ids.size(); i++) {
				preparedStatement.setObject(i + 1, UUID.fromString(ids.get(i)));
			}

			try (ResultSet resultSet = preparedStatement.executeQuery()) {
				Map<String, Map<String, Object>> validationResults = new HashMap<>();

				while (resultSet.next()) {
					String id = resultSet.getString(rule.getIdColumn());
					Map<String, Object> columnValues = new HashMap<>();

					for (String column : rule.getValidationColumns()) {
						Object value = resultSet.getObject(column);
						columnValues.put(column, value);
					}
					validationResults.put(id, columnValues);
				}

				return validationResults;
			} catch (SQLException e) {
				throw new SQLException("Failed to execute query for ValidationRule", e);
			}
		}
	}

	public Map<String, Boolean> validateRequest(BaseRequest request, List<String> ids) throws ValidationException, SQLException, FileNotFoundException {
		RequestValidationRules requestValidationRules = new RequestValidationRules();
		Map<String, Boolean> emptyIdsRules = getRulesForEmptyIds(request, ids, requestValidationRules);

		boolean isIdsFormatted = (request.getRequestValidationRules() != null)
				? request.getRequestValidationRules().getIsIdsFormatted()
				: requestValidationRules.getIsIdsFormatted();

		if (isIdsFormatted) {
			validateByValidationConfig(ids, BaseRequest.class);
		}

		return emptyIdsRules;
	}

	public Map<String, Boolean> validateMilitaryNoticeRequest(BaseRequest request, RequestParameters parameters) throws ValidationException, SQLException, FileNotFoundException {
		RequestValidationRules requestValidationRules = new RequestValidationRules();
		List<String> ids = parameters.getIds();
		Map<String, Boolean> emptyIdsRules = getRulesForEmptyIds(request, ids, requestValidationRules);
		Map<String, Boolean> emptyDatesRules = getRulesForEmptyDates(request, parameters);

		validateByValidationConfig(ids, RequestParameters.class);

		Map<String, Boolean> merged = new HashMap<>();
		merged.putAll(emptyIdsRules);
		merged.putAll(emptyDatesRules);

		return merged;
	}

	public Map<String, Boolean> validateDownloadRequest(BaseDownloadRequest request, RequestParameters downloadRequest, List<String> ids) throws ValidationException {
		RequestValidationRules requestValidationRules = new RequestValidationRules();
		Map<String, Boolean> emptyIdsRules = getRulesForEmptyIds(request, ids, requestValidationRules);
		Map<String, Boolean> emptyDatesRules = getRulesForEmptyDates(request, downloadRequest);

		Map<String, Boolean> merged = new HashMap<>();
		merged.putAll(emptyIdsRules);
		merged.putAll(emptyDatesRules);

		return merged;
	}

	private static Map<String, Boolean> getRulesForEmptyIds(BaseRequest request, List<String> ids, RequestValidationRules defaultRules) {

		if (request.getRequestValidationRules() == null && defaultRules.getIsEmptyIdsAllowed()) {
			return Map.of(ValidationService.IS_EMPTY_IDS_ALLOWED, true);
		}

		if (request.getRequestValidationRules() != null && request.getRequestValidationRules().getIsEmptyIdsAllowed()) {
			return Map.of(ValidationService.IS_EMPTY_IDS_ALLOWED, true);
		}

		if (ids == null || ids.isEmpty()) {
			throw new IllegalRequestParametersException("eks.error.argument.missing");
		}

		boolean isIdsFormatted = (request.getRequestValidationRules() != null && request.getRequestValidationRules().getIsIdsFormatted())
														 || (request.getRequestValidationRules() == null && defaultRules.getIsIdsFormatted());

		if (!isIdsFormatted) {
			return Map.of(ValidationService.IS_EMPTY_IDS_ALLOWED, false);
		}

		String uuidRegex = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
		boolean invalidIdFound = ids.stream().anyMatch(id -> !id.matches(uuidRegex));

		if (!invalidIdFound) {
			return Map.of(ValidationService.IS_EMPTY_IDS_ALLOWED, false);
		}

		throw new IllegalRequestParametersException("eks.error.argument.invalid");

	}

	private static <R extends BaseRequest, T extends RequestParameters> Map<String, Boolean> getRulesForEmptyDates(R request, T parameters) {
		Boolean emptyDatesAllowed = isEmptyDatesAllowed(request, parameters);

		if (!emptyDatesAllowed && parameters.getStartDate() != null && parameters.getEndDate() != null) {
			if (parameters.getStartDate().isAfter(parameters.getEndDate())) {
				throw new IllegalArgumentException("eks.error.date.order");
			}
		}

		return Map.of(ValidationService.IS_EMPTY_DATES_ALLOWED, emptyDatesAllowed);
	}

	private static <T extends RequestParameters> Boolean isEmptyDatesAllowed(BaseRequest request, T parameters) {

		Boolean emptyDatesAllowed = request.getRequestValidationRules() == null
				? Boolean.FALSE
				: request.getRequestValidationRules().getIsEmptyDatesAllowed();

		LocalDate startDate = parameters.getStartDate();
		LocalDate endDate = parameters.getEndDate();

		if (Boolean.FALSE.equals(emptyDatesAllowed) && (startDate == null || endDate == null)) {
			throw new IllegalRequestParametersException("eks.error.date.empty");
		}

		return emptyDatesAllowed;

	}

	public <T> Map<String, String> validateByValidationConfig(List<String> ids, T c) throws ValidationException, FileNotFoundException, SQLException {

		ValidationRules config = configService.getConfig(ConfigType.VALIDATE_BLOCK, ValidationRules.class);

		if (config.getValidationRules() == null || config.getValidationRules().isEmpty()) {
			return null;
		}

		Map<String, Map<String, Object>> validationResults = new HashMap<>();

		for (ValidationRule rule : config.getValidationRules()) {
			validationResults.putAll(validate(ids, rule));
		}

		Map<String, String> invalidRecords = new HashMap<>();

		validationResults.forEach((id, columnValues) -> {
			List<String> invalidColumns = columnValues.entrySet().stream()
				.filter(entry -> Boolean.FALSE.equals(entry.getValue()))
				.map(Map.Entry::getKey)
				.toList();

			if (!invalidColumns.isEmpty()) {
				String message = "Запись " + id + " имеет не отменённые временные меры или подписанные повестки";
				invalidRecords.put(id, message);
			}
		});

		if (!invalidRecords.isEmpty()) {
			throw new ValidationException("Validation failed for some records", invalidRecords);
		}

		return invalidRecords;

	}
}
