package com.r11.tools.repository;

import static com.r11.tools.utilis.RedisAppender.LOG_PREFIX;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.r11.tools.model.Event;
import com.r11.tools.utilis.BusinessKey;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Repository;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

/**
 * Builds Event list based on logs created via {@link com.r11.tools.utilis.TrackingClient} and {@link com.r11.tools.utilis.RedisAppender}
 * @author Rafał Żukowicz
 */
@Repository
@AllArgsConstructor
public class EventRepositoryImpl implements EventRepository {
    private final JedisPool jedisPool;
    private final ObjectMapper objectMapper;

    /**
     * Creates list of {@link Event} based on {@link com.r11.tools.model.EventRequestDto} data via searching logs
     * @param localDateTimeFrom date from which logs are retrieved
     * @param localDateTimeTo date to which logs are retrieved
     * @param businessKeys set keys for redis values
     * @return
     */
    @Override
    public List<Event> findEvents(LocalDateTime localDateTimeFrom, LocalDateTime localDateTimeTo,
                                   Map<BusinessKey, String> businessKeys) {
        List<String> eventStrings = findEventsBetweenDates(localDateTimeFrom.toLocalDate(), localDateTimeTo.toLocalDate());
        if (businessKeys.size() > 0) {
            eventStrings = businessKeysFilter(eventStrings, businessKeys);
        }
        List<Event> events = parseEvents(eventStrings);
        if (localDateTimeFrom.toLocalTime() != LocalTime.MIN) {
            events = events.stream().filter(event -> event.getDateTimeStamp().compareTo(localDateTimeFrom) >= 0)
                    .collect(Collectors.toList());
        }
        return events.stream().filter(event -> event.getDateTimeStamp().compareTo(localDateTimeTo) <= 0)
                .collect(Collectors.toList());
    }

    /**
     * Returns logs between given dates
     * @param localDateFrom date from which logs are retrieved
     * @param localDateTo date to which logs are retrieved
     * @return
     */
    private List<String> findEventsBetweenDates(LocalDate localDateFrom, LocalDate localDateTo) {
        try (Jedis jedis = jedisPool.getResource()) {
            return localDateFrom.datesUntil(localDateTo.plusDays(1)).map(day -> LOG_PREFIX + day.toString())
                    .map(key -> jedis.lrange(key, 0, -1)).flatMap(Collection::stream).collect(Collectors.toList());
        }
    }

    /**
     * Filters keys so only the ones queried are retirned
     * @param events list of logs
     * @param businessKeys set keys for redis values
     * @return filtered list of logs
     */
    private List<String> businessKeysFilter(List<String> events, Map<BusinessKey, String> businessKeys) {
        for (Map.Entry<BusinessKey, String> entry : businessKeys.entrySet()) {
            String stringPattern =  entry.getKey().getReasonPhrase()+ "\"" + ":" + "\"" + entry.getValue() + "\"";
            events = events.stream().filter(s -> s.contains(stringPattern)).collect(Collectors.toList());
        }
        return events;
    }

    /**
     * Parses list of logs into list of {@link Event}
     * @param eventStrings list of logs
     * @return list of {@link Event}
     */
    private List<Event> parseEvents(List<String> eventStrings) {
        List<Event> events = new ArrayList<>();
        for (String eventString : eventStrings) {
            eventString = eventString.replaceAll("\\R", "");
            try {
                events.add(objectMapper.readValue(eventString, Event.class));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return events;
    }
}
