package com.github.fridujo.rabbitmq.mock;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.rabbitmq.client.AMQP;

public interface DeadLettering {

    String X_DEATH_HEADER = "x-death";

    enum ReasonType {
        /**
         * The message was rejected with requeue parameter set to false.
         */
        REJECTED("rejected"),
        /**
         * The message TTL has expired.
         */
        EXPIRED("expired"),
        /**
         * The maximum allowed queue length was exceeded.
         */
        MAX_LEN("maxlen");

        public final String headerValue;

        ReasonType(String headerValue) {
            this.headerValue = headerValue;
        }
    }

    class Event {
        private static final String QUEUE_KEY = "queue";
        private static final String REASON_KEY = "reason";
        private static final String EXCHANGE_KEY = "exchange";
        private static final String ROUTING_KEYS_KEY = "routing-keys";
        private static final String COUNT_KEY = "count";

        private final String queue;
        private final ReasonType reason;
        private final String exchange;
        private final List<String> routingKeys;
        private final long count;

        public Event(String queue, ReasonType reason, Message message, int count) {
            this.queue = queue;
            this.reason = reason;
            this.exchange = message.exchangeName;
            this.routingKeys = Collections.singletonList(message.routingKey);
            this.count = count;
        }

        public Map<String, Object> asHeaderEntry() {
            Map<String, Object> entry = new HashMap<>();
            entry.put(QUEUE_KEY, queue);
            entry.put(REASON_KEY, reason.headerValue);
            entry.put(EXCHANGE_KEY, exchange);
            entry.put(ROUTING_KEYS_KEY, routingKeys);
            entry.put(COUNT_KEY, count);
            return entry;
        }

        @SuppressWarnings("unchecked")
        public AMQP.BasicProperties prependOn(AMQP.BasicProperties props) {
            Map<String, Object> headers = Optional.ofNullable(props.getHeaders()).map(HashMap::new).orElseGet(HashMap::new);

            List<Map<String, Object>> xDeathHeader = (List<Map<String, Object>>) headers.computeIfAbsent(X_DEATH_HEADER, key -> new ArrayList<>());

            Optional<Map<String, Object>> previousEvent = xDeathHeader.stream()
                .filter(this::sameQueueAndReason)
                .findFirst();

            final Map<String, Object> currentEvent;
            if (previousEvent.isPresent()) {
                xDeathHeader.remove(previousEvent.get());
                currentEvent = incrementCount(previousEvent.get());
            } else {
                currentEvent = asHeaderEntry();
            }
            xDeathHeader.add(0, currentEvent);

            return props.builder().headers(Collections.unmodifiableMap(headers)).build();
        }

        private Map<String, Object> incrementCount(Map<String, Object> previousEvent) {
            previousEvent.compute(COUNT_KEY, (key, count) -> (long) count + 1);
            return previousEvent;
        }

        private boolean sameQueueAndReason(Map<String, Object> event) {
            return queue.equals(event.get(QUEUE_KEY)) && reason.headerValue.equals(event.get(REASON_KEY));
        }
    }
}