package ru.yandex.webmaster3.storage.async;

import java.util.UUID;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.worker.client.WorkerClient;
import ru.yandex.webmaster3.core.worker.task.async.AsyncWorkerTaskData;
import ru.yandex.webmaster3.storage.async.model.AsyncOperationRequestInfo;
import ru.yandex.webmaster3.storage.async.model.AsyncTaskType;
import ru.yandex.webmaster3.storage.async.model.HttpAsyncRequestInfoRequestState;
import ru.yandex.webmaster3.storage.async.model.HttpAsyncRequestInfoResultType;
import ru.yandex.webmaster3.storage.async.model.IRequestData;
import ru.yandex.webmaster3.storage.checklist.data.SiteProblemContentSerializer;
import ru.yandex.webmaster3.storage.http.dao.AsyncOperationInfoYDao;
import ru.yandex.webmaster3.storage.util.ydb.exception.WebmasterYdbException;

/**
 * @author: ishalaru
 * DATE: 13.05.2019
 * Service for work with async operation
 */
@Slf4j
@Service("asyncRequestTaskService")
public class AsyncRequestTaskService {
    private static final ObjectMapper OM = new ObjectMapper();

    private final WorkerClient workerClient;
    private final AsyncOperationInfoYDao asyncOperationInfoYDao;

    @Autowired
    public AsyncRequestTaskService(@Qualifier("lbWorkerClient") WorkerClient workerClient,
                                   AsyncOperationInfoYDao asyncOperationInfoYDao) {
        this.workerClient = workerClient;
        this.asyncOperationInfoYDao = asyncOperationInfoYDao;
    }


    /**
     * Save data in storage and send to worker
     *
     * @param modelType      Model type to send in worker
     * @param version        Model version
     * @param data           Data to save in storage
     * @param workerTaskData object to send in worker
     * @return
     */
    public CreateRequestResult createRequest(AsyncTaskType modelType, int version, IRequestData data, AsyncWorkerTaskData workerTaskData) {
        String value;
        try {
            value = OM.writeValueAsString(data);
        } catch (JsonProcessingException e) {
            throw new WebmasterException("Failed to serialize object " + data,
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(SiteProblemContentSerializer.class, ""), e);
        }

        asyncOperationInfoYDao.insert(workerTaskData.getRequestId(), DateTime.now(), modelType, version, value);
        workerClient.enqueueTask(workerTaskData);
        return new CreateRequestResult(CreateRequestResultType.OK, workerTaskData.getRequestId());
    }

    /**
     * Get Response info from storage
     *
     * @param requestId UUID
     * @return AsyncOperationRequestInfo
     */
    public AsyncOperationRequestInfo getResponseInfo(UUID requestId) {
        try {
            return asyncOperationInfoYDao.getRequest(requestId);
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to load data",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    public void startWorkingTask(UUID requestId) {
        try {
            asyncOperationInfoYDao.updateState(requestId, HttpAsyncRequestInfoRequestState.TASK_STARTED);
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to mark task as started",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    public void finishWorkingTask(UUID requestId, int responseObjectType, Object data) {
        String value = null;
        try {
            value = OM.writeValueAsString(data);
        } catch (JsonProcessingException e) {
            throw new WebmasterException("Failed to serialize object " + data,
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(SiteProblemContentSerializer.class, ""), e);
        }
        asyncOperationInfoYDao.saveResponse(requestId, responseObjectType, value);

    }


    public void finishWorkingTask(UUID requestId, HttpAsyncRequestInfoResultType resultType) {
        try {
            asyncOperationInfoYDao.saveFailedTaskInfo(requestId, resultType);
        } catch (WebmasterYdbException e) {
            throw new WebmasterException("Failed to save task result",
                    new WebmasterErrorResponse.YDBErrorResponse(getClass(), e), e);
        }
    }

    public static class CreateRequestResult {
        private final CreateRequestResultType type;
        private final UUID requestId;

        public CreateRequestResult(CreateRequestResultType type, UUID requestId) {
            this.type = type;
            this.requestId = requestId;
        }

        public CreateRequestResultType getType() {
            return type;
        }

        public UUID getRequestId() {
            return requestId;
        }
    }

    public enum CreateRequestResultType {
        OK,
    }

}
