package com.cameleer.server.core.search;

import java.time.Instant;
import java.util.List;

/**
 * Immutable search criteria for querying route executions.
 * <p>
 * All filter fields are nullable/optional. When null, the filter is not applied.
 * The compact constructor validates and normalizes pagination parameters.
 *
 * @param status             execution status filter (COMPLETED, FAILED, RUNNING)
 * @param timeFrom           inclusive start of time range
 * @param timeTo             exclusive end of time range
 * @param durationMin        minimum duration in milliseconds (inclusive)
 * @param durationMax        maximum duration in milliseconds (inclusive)
 * @param correlationId      exact correlation ID match
 * @param text               global full-text search across all text fields
 * @param textInBody         full-text search scoped to exchange bodies
 * @param textInHeaders      full-text search scoped to exchange headers
 * @param textInErrors       full-text search scoped to error messages and stack traces
 * @param routeId            exact match on route_id
 * @param instanceId         exact match on instance_id
 * @param processorType      matches processor_types array via has()
 * @param applicationId      exact match on application_id
 * @param instanceIds        list of instance IDs for an IN clause (only set when drilling down to specific agents)
 * @param offset             pagination offset (0-based)
 * @param limit              page size (default 50, max 500)
 * @param sortField          column to sort by (default: startTime)
 * @param sortDir            sort direction: asc or desc (default: desc)
 * @param afterExecutionId   when combined with a non-null {@code timeFrom}, applies the composite predicate
 *                           {@code (start_time > timeFrom) OR (start_time = timeFrom AND execution_id > afterExecutionId)}.
 *                           When null, {@code timeFrom} is applied as a plain {@code >=} lower bound (existing behaviour).
 * @param environment        optional environment filter (e.g. "dev", "staging", "prod")
 */
public record SearchRequest(
        String status,
        Instant timeFrom,
        Instant timeTo,
        Long durationMin,
        Long durationMax,
        String correlationId,
        String text,
        String textInBody,
        String textInHeaders,
        String textInErrors,
        String routeId,
        String instanceId,
        String processorType,
        String applicationId,
        List<String> instanceIds,
        int offset,
        int limit,
        String sortField,
        String sortDir,
        String afterExecutionId,
        String environment,
        List<AttributeFilter> attributeFilters
) {

    private static final int DEFAULT_LIMIT = 50;
    private static final int MAX_LIMIT = 500;

    private static final java.util.Set<String> ALLOWED_SORT_FIELDS = java.util.Set.of(
            "startTime", "status", "instanceId", "routeId", "correlationId",
            "durationMs", "executionId", "applicationId"
    );

    /** Maps camelCase API sort field names to storage column names. */
    private static final java.util.Map<String, String> SORT_FIELD_TO_COLUMN = java.util.Map.ofEntries(
            java.util.Map.entry("startTime", "start_time"),
            java.util.Map.entry("durationMs", "duration_ms"),
            java.util.Map.entry("status", "status.keyword"),
            java.util.Map.entry("instanceId", "instance_id.keyword"),
            java.util.Map.entry("routeId", "route_id.keyword"),
            java.util.Map.entry("correlationId", "correlation_id.keyword"),
            java.util.Map.entry("executionId", "execution_id.keyword"),
            java.util.Map.entry("applicationId", "application_id.keyword")
    );

    public SearchRequest {
        if (limit <= 0) limit = DEFAULT_LIMIT;
        if (limit > MAX_LIMIT) limit = MAX_LIMIT;
        if (offset < 0) offset = 0;
        if (sortField == null || !ALLOWED_SORT_FIELDS.contains(sortField)) sortField = "startTime";
        if (!"asc".equalsIgnoreCase(sortDir)) sortDir = "desc";
        if (attributeFilters == null) attributeFilters = List.of();
    }

    /** Legacy 21-arg constructor preserved for existing call sites — defaults attributeFilters to empty. */
    public SearchRequest(
            String status, Instant timeFrom, Instant timeTo,
            Long durationMin, Long durationMax, String correlationId,
            String text, String textInBody, String textInHeaders, String textInErrors,
            String routeId, String instanceId, String processorType,
            String applicationId, List<String> instanceIds,
            int offset, int limit, String sortField, String sortDir,
            String afterExecutionId, String environment
    ) {
        this(status, timeFrom, timeTo, durationMin, durationMax, correlationId,
                text, textInBody, textInHeaders, textInErrors,
                routeId, instanceId, processorType, applicationId, instanceIds,
                offset, limit, sortField, sortDir, afterExecutionId, environment,
                List.of());
    }

    /** Returns the snake_case column name for ORDER BY. */
    public String sortColumn() {
        return SORT_FIELD_TO_COLUMN.getOrDefault(sortField, "start_time");
    }

    /** Create a copy with resolved instanceIds (from application ID lookup). */
    public SearchRequest withInstanceIds(List<String> resolvedInstanceIds) {
        return new SearchRequest(
                status, timeFrom, timeTo, durationMin, durationMax, correlationId,
                text, textInBody, textInHeaders, textInErrors,
                routeId, instanceId, processorType, applicationId, resolvedInstanceIds,
                offset, limit, sortField, sortDir, afterExecutionId, environment,
                attributeFilters
        );
    }

    /** Create a copy with the given environment filter. */
    public SearchRequest withEnvironment(String env) {
        return new SearchRequest(
                status, timeFrom, timeTo, durationMin, durationMax, correlationId,
                text, textInBody, textInHeaders, textInErrors,
                routeId, instanceId, processorType, applicationId, instanceIds,
                offset, limit, sortField, sortDir, afterExecutionId, env,
                attributeFilters
        );
    }

    /**
     * Create a copy with a composite {@code (start_time, execution_id)} cursor.
     * <p>
     * The resulting request applies a strictly-after tuple predicate
     * {@code (start_time > ts) OR (start_time = ts AND execution_id > afterExecutionId)},
     * enabling exactly-once consumption of same-millisecond exchanges across scheduler ticks.
     */
    public SearchRequest withCursor(Instant ts, String afterExecutionId) {
        return new SearchRequest(
                status, ts, timeTo, durationMin, durationMax, correlationId,
                text, textInBody, textInHeaders, textInErrors,
                routeId, instanceId, processorType, applicationId, instanceIds,
                offset, limit, sortField, sortDir, afterExecutionId, environment,
                attributeFilters
        );
    }
}
