package at.procon.dip.search.repository;

import at.procon.dip.domain.access.DocumentVisibility;
import at.procon.dip.search.api.SearchExecutionContext;
import at.procon.dip.search.dto.SearchRequest;
import jakarta.persistence.Query;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

abstract class AbstractNativeSearchRepository {

    protected int engineLimit(SearchExecutionContext context) {
        return Math.max(50, (context.getPage() + 1) * context.getSize() * 3);
    }

    protected void appendGenericFilters(StringBuilder sql, Map<String, Object> params, SearchExecutionContext context) {
        SearchRequest request = context.getRequest();

        appendEnumInClause(sql, params, "d.document_type::text", "documentType", request.getDocumentTypes());
        appendEnumInClause(sql, params, "d.document_family::text", "documentFamily", request.getDocumentFamilies());

        Collection<DocumentVisibility> visibilities = request.getVisibilities();
        if ((visibilities == null || visibilities.isEmpty()) && context.getScope() != null) {
            visibilities = context.getScope().visibilities();
        }
        appendEnumInClause(sql, params, "d.visibility::text", "visibility", visibilities);

        Collection<String> ownerTenantKeys = context.getScope() == null ? null : context.getScope().ownerTenantKeys();
        if (ownerTenantKeys != null && !ownerTenantKeys.isEmpty()) {
            appendStringInClause(sql, params, "COALESCE(dt.tenant_key, '')", "tenantKey", ownerTenantKeys);
        }

        Collection<String> languageCodes = request.getLanguageCodes();
        if ((languageCodes == null || languageCodes.isEmpty()) && context.getScope() != null && context.getScope().languageCode() != null) {
            languageCodes = java.util.List.of(context.getScope().languageCode());
        }
        appendStringInClause(sql, params, "COALESCE(dtr.language_code, d.language_code, '')", "languageCode", languageCodes);

        appendEnumInClause(sql, params, "dtr.representation_type::text", "representationType", request.getRepresentationTypes());

        if (request.getCreatedFrom() != null) {
            sql.append(" AND d.created_at >= :createdFrom");
            params.put("createdFrom", request.getCreatedFrom());
        }
        if (request.getCreatedTo() != null) {
            sql.append(" AND d.created_at <= :createdTo");
            params.put("createdTo", request.getCreatedTo());
        }
    }

    protected void bindParameters(Query query, Map<String, Object> params) {
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            query.setParameter(entry.getKey(), entry.getValue());
        }
    }

    protected Map<String, Object> newParams() {
        return new LinkedHashMap<>();
    }

    protected void appendEnumInClause(StringBuilder sql, Map<String, Object> params, String expression, String baseParam, Collection<?> values) {
        if (values == null || values.isEmpty()) {
            return;
        }
        sql.append(" AND ").append(expression).append(" IN (");
        int i = 0;
        for (Object value : values) {
            String param = baseParam + i++;
            if (i > 1) {
                sql.append(", ");
            }
            sql.append(':').append(param);
            params.put(param, value.toString());
        }
        sql.append(')');
    }

    protected void appendStringInClause(StringBuilder sql, Map<String, Object> params, String expression, String baseParam, Collection<String> values) {
        if (values == null || values.isEmpty()) {
            return;
        }
        sql.append(" AND ").append(expression).append(" IN (");
        int i = 0;
        for (String value : values) {
            String param = baseParam + i++;
            if (i > 1) {
                sql.append(", ");
            }
            sql.append(':').append(param);
            params.put(param, value);
        }
        sql.append(')');
    }

    protected UUID asUuid(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof UUID uuid) {
            return uuid;
        }
        return UUID.fromString(value.toString());
    }

    protected OffsetDateTime asOffsetDateTime(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof OffsetDateTime odt) {
            return odt;
        }
        if (value instanceof Timestamp timestamp) {
            return timestamp.toInstant().atOffset(ZoneOffset.UTC);
        }
        if (value instanceof LocalDateTime ldt) {
            return ldt.atOffset(ZoneOffset.UTC);
        }
        throw new IllegalArgumentException("Unsupported timestamp value: " + value.getClass());
    }

    protected String asString(Object value) {
        return value == null ? null : value.toString();
    }

    protected Double asDouble(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.doubleValue();
        }
        return Double.parseDouble(value.toString());
    }
}
