package ru.yandex.webmaster3.worker.messages;

import java.time.LocalDate;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

import ru.yandex.webmaster3.core.worker.task.PeriodicTaskState;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.core.worker.task.TaskResult;
import ru.yandex.webmaster3.storage.user.dao.UserHostVerificationYDao;
import ru.yandex.webmaster3.storage.user.service.NewUserMessagesService;
import ru.yandex.webmaster3.storage.util.yt.YtNode;
import ru.yandex.webmaster3.storage.util.yt.YtNodeAttributes;
import ru.yandex.webmaster3.storage.util.yt.YtPath;
import ru.yandex.webmaster3.storage.util.yt.YtService;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.TaskSchedule;


@RequiredArgsConstructor
public class ExportUserMessages3Task extends PeriodicTask<PeriodicTaskState> {
    private final YtPath tablePath;

    private final YtService ytService;
    private final NewUserMessagesService newUserMessagesService;
    private final UserHostVerificationYDao userHostVerificationYDao;

    private static final int MAX_THREADS_READERS = 15;

    @Override
    public Result run(UUID runId) throws Exception {
        YtPath path = YtPath.path(tablePath, String.valueOf(LocalDate.now()));

        ytService.inTransaction(path).execute(cypressService -> {
            cypressService.create(path, YtNode.NodeType.TABLE, true, new YtNodeAttributes(), true);

            ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS_READERS + 1);
            Queue<Long> queueUsers = new ConcurrentLinkedQueue<>();
            Queue<UserCount> queueCounts = new ConcurrentLinkedQueue<>();

            AtomicBoolean inWork = new AtomicBoolean(true);
            AtomicInteger activeReader = new AtomicInteger(0);

            //Read data from db and put
            for (int i = 0; i < MAX_THREADS_READERS; i++) {
                executor.execute(() -> {
                    activeReader.addAndGet(1);

                    while (inWork.get() || !queueUsers.isEmpty()) {
                        Long userId = queueUsers.poll();
                        if (userId != null) {
                            queueCounts.add(
                                        new UserCount(userId, newUserMessagesService.unreadCount(userId, true))
                                );
                        }
                    }

                    activeReader.addAndGet(-1);
                });
            }


            //Start add tasks to read users
            // TODO
            /*executor.execute(()-> {

                userHostVerificationYDao.forEachUser(userId ->{
                    queueUsers.add(userId);

                    while (queueUsers.size() > 1_000) busySleep(100);
                });

                inWork.set(false); //End hosts
            });*/

            cypressService.writeTable(path, tableWriter -> {
                while (activeReader.get() > 0 || !queueCounts.isEmpty()) {
                    UserCount userCount = queueCounts.poll();
                    if (userCount != null) {
                        tableWriter.column("user_id", userCount.getUserId());
                        tableWriter.column("unread_count", userCount.getCount());

                        tableWriter.rowEnd();

                        while (queueUsers.isEmpty() && activeReader.get() > 0) busySleep(100);
                    }
                }
            });

            executor.shutdown();
            executor.awaitTermination(1, TimeUnit.MINUTES);

            return true;
        });



        return new Result(TaskResult.SUCCESS);
    }

    @Override
    public PeriodicTaskType getType() {
        return PeriodicTaskType.EXPORT_DAILY_USER_MESSAGES;
    }

    @SneakyThrows
    private void busySleep(int millis) {
        Thread.sleep(millis);
    }

    @Override
    public TaskSchedule getSchedule() {
        return TaskSchedule.startByCron("0 0 21 * * *");
    }

    @Data
    @AllArgsConstructor
    static class UserCount{
        private Long userId, count;
    }
}

