package ru.yandex.webmaster3.core.log;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.annotation.PostConstruct;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.http.WebmasterJsonModule;
import ru.yandex.webmaster3.core.logbroker.writer.LogbrokerClient;
import ru.yandex.webmaster3.core.util.RetryUtils;

/**
 * ishalaru
 * 25.12.2020
 **/
@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
@Slf4j
public class EventLogService {
    private static final ObjectMapper OM = new ObjectMapper()
            .registerModule(new WebmasterJsonModule(false))
            .registerModule(new JodaModule())
            .setSerializationInclusion(JsonInclude.Include.NON_NULL)
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    private static final int BATCH_SIZE = 50;
    private final BlockingQueue<byte[]> sendQueue = new ArrayBlockingQueue<>(500);
    private ExecutorService sendQueueExecutor;
    private final LogbrokerClient eventsLogbrokerClient;


    @PostConstruct
    public void init() {
        sendQueueExecutor = Executors.newSingleThreadExecutor(
                new ThreadFactoryBuilder()
                        .setNameFormat("logfeller-client-queue-%d")
                        .setDaemon(true)
                        .build()
        );
        sendQueueExecutor.execute(new EventLogService.SendWorker());
    }

    public byte[] convert(LogfellerEvent logfellerEvent) {
        try {
            return OM.writeValueAsBytes(logfellerEvent);
        } catch (JsonProcessingException e) {
            throw new WebmasterException("Failed to serialize user message of type " + logfellerEvent.getObserver(),
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(EventLogService.class,
                            "Failed to serialize user message of type " + logfellerEvent.getObserver()), e);
        }
    }

    public void checkedSend(LogfellerEvent event) {
        send(List.of(convert(event)));
    }

    public void sendEvent(LogfellerEvent event) {
        sendQueue.add(convert(event));
    }

    public void sendEvents(List<LogfellerEvent> events) {
        for (LogfellerEvent event : events) {
            sendEvent(event);
        }
    }


    private void send(List<byte[]> eventList) {

        try {
            RetryUtils.execute(RetryUtils.linearBackoff(3, Duration.standardSeconds(10)), () -> {
                eventsLogbrokerClient.write(eventList, Duration.standardSeconds(20));
            });
        } catch (Exception e) {
            log.error("Failed to send event to Logbroker: " + eventList, e);
        }
    }


    private class SendWorker implements Runnable {
        @Override
        public void run() {
            while (!Thread.interrupted()) {
                byte[] event = null;
                try {
                    event = sendQueue.take();
                } catch (InterruptedException e) {
                    continue;
                }
                List<byte[]> taskDataList = new ArrayList<>(BATCH_SIZE);
                taskDataList.add(event);
                // затягиваем до BATCH_SIZE элементов (1 уже есть)
                sendQueue.drainTo(taskDataList, BATCH_SIZE - 1);
                send(taskDataList);
            }
        }
    }


}
