package ru.yandex.webmaster3.worker.http;

import java.util.UUID;

import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.Action;
import ru.yandex.webmaster3.core.http.ActionRequest;
import ru.yandex.webmaster3.core.http.ActionResponse;
import ru.yandex.webmaster3.core.http.RequestQueryProperty;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.http.WebmasterJsonModule;
import ru.yandex.webmaster3.core.worker.task.PeriodicTaskType;
import ru.yandex.webmaster3.core.worker.task.TaskResult;
import ru.yandex.webmaster3.core.worker.task.WorkerTaskData;
import ru.yandex.webmaster3.core.worker.task.WorkerTaskType;
import ru.yandex.webmaster3.worker.PeriodicTask;
import ru.yandex.webmaster3.worker.Task;
import ru.yandex.webmaster3.worker.TaskRegistry;
import ru.yandex.webmaster3.worker.queue.TaskQueueService;

/**
 * @author aherman
 */
public class RunTaskAction extends Action<RunTaskAction.Request, RunTaskAction.Response> {
    private static final Logger log = LoggerFactory.getLogger(RunTaskAction.class);

    @Autowired
    private TaskRegistry taskRegistry;

    private static final ObjectMapper OM = new ObjectMapper()
            .registerModules(new JodaModule(), new WebmasterJsonModule(false))
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

    @Override
    public Response process(Request request) throws WebmasterException {
        if (request.getTaskType() != null) {
            if (taskRegistry.getTaskRegistryMap().containsKey(request.getTaskType())) {
                Task task = taskRegistry.getTaskRegistryMap().get(request.getTaskType());
                return runSimpleTask(request, task);
            } else {
                throw new WebmasterException("Error running task",
                        new WebmasterErrorResponse.InternalUnknownErrorResponse(this.getClass(),
                                "Error running task - unknown task type"));
            }
        } else if (request.getPeriodicTaskType() != null) {
            if (taskRegistry.getPeriodicTaskRegistryMap().containsKey(request.getPeriodicTaskType())) {
                PeriodicTask task = taskRegistry.getPeriodicTaskRegistryMap().get(request.getPeriodicTaskType());
                return runPeriodicTask(request, task);
            } else {
                throw new WebmasterException("Error running periodic task",
                        new WebmasterErrorResponse.InternalUnknownErrorResponse(this.getClass(),
                                "Error running periodic task - unknown periodic task type"));
            }
        } else {
            throw new WebmasterException("Error running task",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(this.getClass(),
                            "Error running task - unknown task type"));
        }
    }

    @NotNull
    private Response runPeriodicTask(Request request, PeriodicTask task) {
        UUID runId = UUIDs.timeBased();
        try (MDC.MDCCloseable mdcCloseable = MDC.putCloseable(
                TaskQueueService.MDC_TASK_ID_KEY, runId.toString())) {
            TaskResult taskResult = task.execute(runId).getTaskResult();
            return new Response(taskResult, task.getState());
        } catch (Exception e) {
            throw new WebmasterException("Error running periodic task",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(this.getClass(),
                            "Error running periodic task: " + e.getMessage()), e);
        }
    }

    @NotNull
    private Response runSimpleTask(Request request, Task task) {
        try (MDC.MDCCloseable mdcCloseable = MDC.putCloseable(TaskQueueService.MDC_TASK_ID_KEY, request.getTaskType().toString())) {
            log.info("Task parameter type: {}", task.getDataClass());
            String taskData = request.getTaskData();

            TaskResult taskResult;
            if (StringUtils.isEmpty(taskData)) {
                taskResult = task.runWithTracer(null).getTaskResult();
            } else {
                WorkerTaskData data = (WorkerTaskData) OM.readValue(taskData, task.getDataClass());
                log.info("Task data: {}", OM.writeValueAsString(data));
                taskResult = task.runWithTracer(data).getTaskResult();
            }
            return new Response(taskResult);
        } catch (Exception e) {
            throw new WebmasterException("Error running task",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(this.getClass(),
                            "Error running task: " + e.getMessage()), e);
        }
    }

    public static class Request implements ActionRequest {
        private WorkerTaskType taskType;
        private PeriodicTaskType periodicTaskType;
        private String taskData;

        public WorkerTaskType getTaskType() {
            return taskType;
        }

        @RequestQueryProperty
        public void setTaskType(WorkerTaskType taskType) {
            this.taskType = taskType;
        }

        public PeriodicTaskType getPeriodicTaskType() {
            return periodicTaskType;
        }

        @RequestQueryProperty
        public void setPeriodicTaskType(PeriodicTaskType periodicTaskType) {
            this.periodicTaskType = periodicTaskType;
        }

        public String getTaskData() {
            return taskData;
        }

        @RequestQueryProperty
        public void setTaskData(String taskData) {
            this.taskData = taskData;
        }
    }

    public static class Response implements ActionResponse.NormalResponse {
        private final TaskResult taskResult;
        private final Object periodicTaskState;

        public Response(TaskResult taskResult) {
            this.taskResult = taskResult;
            this.periodicTaskState = null;
        }

        public Response(TaskResult taskResult, Object periodicTaskState) {
            this.taskResult = taskResult;
            this.periodicTaskState = periodicTaskState;
        }

        public TaskResult getTaskResult() {
            return taskResult;
        }

        public Object getPeriodicTaskState() {
            return periodicTaskState;
        }
    }
}
