package at.procon.eventhub.processing.support;

import at.procon.eventhub.dto.EventDetailsDto;
import at.procon.eventhub.dto.EventDomain;
import at.procon.eventhub.dto.EventHubEventDto;
import at.procon.eventhub.dto.EventLifecycle;
import at.procon.eventhub.dto.EventType;
import com.fasterxml.jackson.databind.JsonNode;
import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;

public final class RuntimeEventIdentityResolver {

    private RuntimeEventIdentityResolver() {
    }

    public static String canonicalEventKey(EventHubEventDto event) {
        if (event == null) {
            return "NULL_EVENT";
        }
        JsonNode raw = rawPayload(event);
        JsonNode details = detailAttributes(event);
        List<String> parts = new ArrayList<>();
        parts.add("EVENT");
        parts.add(nullToEmpty(event.packageInfo() == null ? null : event.packageInfo().tenantKey()));
        parts.add(nullToEmpty(event.eventDomain() == null ? null : event.eventDomain().name()));
        parts.add(nullToEmpty(event.eventType() == null ? null : event.eventType().name()));
        parts.add(nullToEmpty(event.lifecycle() == null ? null : event.lifecycle().name()));
        parts.add(normalizeTime(event.occurredAt()));
        parts.add(nullToEmpty(TachographRuntimeIdentityResolver.driverKey(event)));
        parts.add(nullToEmpty(TachographRuntimeIdentityResolver.registrationKey(event)));
        parts.add(nullToEmpty(TachographRuntimeIdentityResolver.vehicleKey(event)));
        parts.add(isIntervalPointEvent(event) ? nullToEmpty(runtimeIntervalKey(event)) : "");
        parts.add(nullToEmpty(firstNonBlank(text(raw, "activityType"), event.eventType() == null ? null : event.eventType().name())));
        parts.add(nullToEmpty(firstNonBlank(text(raw, "slot"), text(raw, "cardSlot"), text(details, "cardSlot"))));
        parts.add(nullToEmpty(firstNonBlank(text(raw, "cardStatus"), text(details, "cardStatus"))));
        parts.add(nullToEmpty(firstNonBlank(text(raw, "drivingStatus"), text(details, "drivingStatus"))));
        parts.add(nullToEmpty(firstNonBlank(text(raw, "supportEventType"), event.eventType() == null ? null : event.eventType().name())));
        parts.add(nullToEmpty(firstNonBlank(text(raw, "country"), text(details, "country"))));
        parts.add(nullToEmpty(text(raw, "region")));
        parts.add(nullToEmpty(firstNonBlank(text(raw, "countryFrom"), text(details, "countryFrom"))));
        parts.add(nullToEmpty(firstNonBlank(text(raw, "countryTo"), text(details, "countryTo"))));
        parts.add(nullToEmpty(firstNonBlank(text(raw, "operation"), text(details, "operation"))));
        parts.add(nullToEmpty(text(raw, "authenticationStatus")));
        parts.add(nullToEmpty(text(raw, "code")));
        parts.add(nullToEmpty(firstNonBlank(
                numberText(firstNonBlankNode(raw, "odometerKm")),
                numberText(firstNonBlankNode(raw, "odometerM"))
        )));
        parts.add(nullToEmpty(positionKey(event)));
        return String.join("|", parts);
    }

    public static String runtimeIntervalKey(EventHubEventDto event) {
        if (event == null) {
            return null;
        }
        String originalIntervalId = presentationIntervalId(event);
        if (!isIntervalPointEvent(event)) {
            return originalIntervalId;
        }
        JsonNode raw = rawPayload(event);
        OffsetDateTime startedAt = intervalStartedAt(raw);
        OffsetDateTime endedAt = intervalEndedAt(raw);
        if (startedAt == null && endedAt == null) {
            return originalIntervalId;
        }
        JsonNode details = detailAttributes(event);
        List<String> parts = new ArrayList<>();
        parts.add("INTERVAL");
        parts.add(nullToEmpty(event.eventDomain() == null ? null : event.eventDomain().name()));
        parts.add(nullToEmpty(intervalSemanticType(event, raw)));
        parts.add(nullToEmpty(TachographRuntimeIdentityResolver.driverKey(event)));
        parts.add(nullToEmpty(TachographRuntimeIdentityResolver.registrationKey(event)));
        parts.add(nullToEmpty(TachographRuntimeIdentityResolver.vehicleKey(event)));
        parts.add(nullToEmpty(intervalCardSlot(event, raw, details)));
        parts.add(nullToEmpty(intervalCardStatus(event, raw, details)));
        parts.add(nullToEmpty(intervalDrivingStatus(event, raw, details)));
        parts.add(normalizeTime(startedAt));
        parts.add(normalizeTime(endedAt));
        return String.join("|", parts);
    }

    public static String presentationIntervalId(EventHubEventDto event) {
        JsonNode raw = rawPayload(event);
        return firstNonBlank(
                text(raw, "intervalId"),
                text(raw, "sourceRowId"),
                event == null ? null : event.externalSourceEventId()
        );
    }

    private static boolean isIntervalPointEvent(EventHubEventDto event) {
        if (event == null || event.eventDomain() == null || event.lifecycle() == null) {
            return false;
        }
        if (event.eventDomain() == EventDomain.DRIVER_ACTIVITY) {
            return event.lifecycle() == EventLifecycle.START || event.lifecycle() == EventLifecycle.END;
        }
        if (event.eventDomain() != EventDomain.DRIVER_CARD) {
            return false;
        }
        return event.eventType() == EventType.CARD_INSERTED || event.eventType() == EventType.CARD_WITHDRAWN;
    }

    private static OffsetDateTime intervalStartedAt(JsonNode raw) {
        return time(firstNonBlank(text(raw, "startedAt"), text(raw, "intervalStartedAt")));
    }

    private static OffsetDateTime intervalEndedAt(JsonNode raw) {
        return time(firstNonBlank(text(raw, "endedAt"), text(raw, "intervalEndedAt")));
    }

    private static String intervalSemanticType(EventHubEventDto event, JsonNode raw) {
        if (event == null) {
            return null;
        }
        if (event.eventDomain() == EventDomain.DRIVER_ACTIVITY) {
            return firstNonBlank(text(raw, "activityType"), event.eventType() == null ? null : event.eventType().name());
        }
        if (event.eventDomain() == EventDomain.DRIVER_CARD) {
            return "CARD_USAGE";
        }
        return event.eventType() == null ? null : event.eventType().name();
    }

    private static String intervalCardSlot(EventHubEventDto event, JsonNode raw, JsonNode details) {
        return firstNonBlank(text(raw, "slot"), text(raw, "cardSlot"), text(details, "cardSlot"));
    }

    private static String intervalCardStatus(EventHubEventDto event, JsonNode raw, JsonNode details) {
        if (event != null && event.eventDomain() == EventDomain.DRIVER_CARD) {
            return null;
        }
        return firstNonBlank(text(raw, "cardStatus"), text(details, "cardStatus"));
    }

    private static String intervalDrivingStatus(EventHubEventDto event, JsonNode raw, JsonNode details) {
        if (event != null && event.eventDomain() == EventDomain.DRIVER_CARD) {
            return null;
        }
        return firstNonBlank(text(raw, "drivingStatus"), text(details, "drivingStatus"));
    }

    private static String positionKey(EventHubEventDto event) {
        if (event == null || event.position() == null) {
            return null;
        }
        return nullToEmpty(event.position().latitude()) + ":" + nullToEmpty(event.position().longitude());
    }

    private static JsonNode rawPayload(EventHubEventDto event) {
        return TachographRuntimeIdentityResolver.rawPayload(event);
    }

    private static JsonNode detailAttributes(EventHubEventDto event) {
        EventDetailsDto details = event == null ? null : event.eventDetails();
        return details == null ? null : details.attributes();
    }

    private static JsonNode firstNonBlankNode(JsonNode node, String field) {
        if (node == null || field == null) {
            return null;
        }
        JsonNode value = node.get(field);
        if (value == null || value.isNull()) {
            return null;
        }
        if (value.isTextual() && value.asText().isBlank()) {
            return null;
        }
        return value;
    }

    private static String numberText(JsonNode value) {
        if (value == null || value.isNull()) {
            return null;
        }
        return value.isNumber() ? value.numberValue().toString() : text(value, null);
    }

    private static OffsetDateTime time(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        try {
            return OffsetDateTime.parse(value.trim());
        } catch (DateTimeParseException ignored) {
            return null;
        }
    }

    private static String normalizeTime(OffsetDateTime value) {
        return value == null ? "" : value.toInstant().toString();
    }

    private static String text(JsonNode node, String field) {
        if (node == null) {
            return null;
        }
        JsonNode value = field == null ? node : node.get(field);
        if (value == null || value.isNull()) {
            return null;
        }
        String text = value.asText(null);
        return text == null || text.isBlank() ? null : text.trim();
    }

    private static String firstNonBlank(String... values) {
        if (values == null) {
            return null;
        }
        for (String value : values) {
            if (value != null && !value.isBlank()) {
                return value.trim();
            }
        }
        return null;
    }

    private static String nullToEmpty(Object value) {
        return value == null ? "" : String.valueOf(value);
    }
}
