package ru.yandex.webmaster3.internal.turbo.logo;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.Value;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import ru.yandex.autodoc.common.doc.annotation.Description;
import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.ActionResponse;
import ru.yandex.webmaster3.core.http.FileParameter;
import ru.yandex.webmaster3.core.http.RequestFileProperty;
import ru.yandex.webmaster3.core.http.RequestQueryProperty;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.http.WriteAction;
import ru.yandex.webmaster3.core.metrics.Category;
import ru.yandex.webmaster3.core.turbo.model.TurboHostHeaderType;
import ru.yandex.webmaster3.core.util.GzipUtils;
import ru.yandex.webmaster3.internal.common.InternalAction;
import ru.yandex.webmaster3.internal.common.request.AbstractUserVerifiedHostRequest;
import ru.yandex.webmaster3.internal.common.security.ActionInternalGrant;
import ru.yandex.webmaster3.internal.common.security.InternalGrant;
import ru.yandex.webmaster3.storage.avatars.AvatarImageStoreService;
import ru.yandex.webmaster3.storage.avatars.AvatarPicture;
import ru.yandex.webmaster3.storage.avatars.AvatarsException;
import ru.yandex.webmaster3.storage.avatars.UploadPictureResult;
import ru.yandex.webmaster3.storage.turbo.logo.TurboLogoData;
import ru.yandex.webmaster3.storage.turbo.logo.TurboLogoYDao;

import static ru.yandex.webmaster3.core.turbo.model.TurboHostHeaderType.HORIZONTAL;
import static ru.yandex.webmaster3.core.turbo.model.TurboHostHeaderType.SQUARE;

/**
 * TODO вынести общий код
 */
@WriteAction
@Description(value = "Загрузить логотип в сторадж")
@Category("turbo")
@Component("/turbo/logo/upload")
@ActionInternalGrant(InternalGrant.TURBO)
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class UploadTurboLogoAction extends InternalAction<UploadTurboLogoAction.Request, UploadTurboLogoAction.Response> {
    private static final Logger log = LoggerFactory.getLogger(UploadTurboLogoAction.class);

    private static final int MAX_LOGO_FILE_SIZE = 1024 * 1024;
    private static final int MAX_SVG_LOGO_FILE_SIZE = 10 * 1024;

    private final TurboLogoYDao turboLogoYDao;
    private final AvatarImageStoreService avatarImageStoreService;

    private static long toDataArray(InputStream contentInputStream, ByteArrayOutputStream baso) throws IOException {
        contentInputStream = GzipUtils.unGzip(new BufferedInputStream(contentInputStream, 8));
        return IOUtils.copyLarge(contentInputStream, baso);
    }

    @Override
    public Response process(Request request) {
        String logoFileName;
        UploadPictureResult uploadLogoResult;

        if (request.getLogoUrl() != null) {
            logoFileName = request.getLogoUrl();
            log.debug("Download turbo logo url = {}", logoFileName);
            try {
                uploadLogoResult = avatarImageStoreService.uploadPicture(request.getLogoUrl(), (Integer) null);
            } catch (AvatarsException e) {
                return new Response.UnsupportedFormatResponse(this.getClass());
            }
        } else if (request.getLogoFile() != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream(MAX_LOGO_FILE_SIZE);
            logoFileName = request.getLogoFile().getFileName();
            log.debug("Turbo logo fileName = {}", logoFileName);

            InputStream inputStream = null;
            long logoSize;
            try {
                inputStream = request.getLogoFile().getInputStream();
                logoSize = toDataArray(inputStream, baos);
                log.debug("logo file size = {}", logoSize);
            } catch (IOException e) {
                throw new WebmasterException("IO error occurred while download turbo logo from file " + logoFileName,
                        new WebmasterErrorResponse.IllegalFileParameterResponse(getClass(), "logoFile", null), e);
            } finally {
                IOUtils.closeQuietly(inputStream);
            }
            byte[] logoData = baos.toByteArray();
            if (logoData == null) {
                throw new WebmasterException("No logo in file " + logoFileName,
                        new WebmasterErrorResponse.IllegalFileParameterResponse(this.getClass(), "logoFile", null));
            }
            if (logoSize > MAX_LOGO_FILE_SIZE) {
                return new Response.LogoIsTooLongResponse(this.getClass(), "logo have size = " + logoSize
                        + ", but maximum acceptable size = " + MAX_LOGO_FILE_SIZE);
            }
            if (logoSize == 0) {
                return new Response.LogoIsEmptyResponse(this.getClass());
            }
            try {
                uploadLogoResult = avatarImageStoreService.uploadPicture(logoFileName, baos.toByteArray());
                if (uploadLogoResult.isSvg() && logoSize > MAX_SVG_LOGO_FILE_SIZE) {
                    return new Response.LogoIsTooLongResponse(this.getClass(), "svg logo have size = " + logoSize
                            + ", but maximum acceptable size = " + MAX_SVG_LOGO_FILE_SIZE);
                }
            } catch (AvatarsException e) {
                return new Response.UnsupportedFormatResponse(this.getClass());
            }
        } else {
            throw new WebmasterException("No logo",
                    new WebmasterErrorResponse.IllegalParameterValueResponse(this.getClass(), "logoFile", null));
        }

        if (uploadLogoResult.getInvalidPictureErrorCode() != null) {
            if (uploadLogoResult.getInvalidPictureErrorCode() == UploadPictureResult.InvalidPictureErrorCode.IMAGE_IS_TOO_SMALL) {
                return new Response.LogoIsTooSmallResponse(this.getClass());
            }
            if (uploadLogoResult.getInvalidPictureErrorCode() == UploadPictureResult.InvalidPictureErrorCode.UNABLE_TO_DOWNLOAD_IMAGE_BY_URL) {
                return new Response.UnableToDownloadLogoByUrlResponse(this.getClass());
            }
        }

        List<AvatarPicture> logos = uploadLogoResult.getPictures();
        if (logos == null || logos.isEmpty() || logos.get(0) == null) {
            throw new WebmasterException("Failed to upload turbo logo",
                    new WebmasterErrorResponse.AvatarsErrorResponse(getClass(), null, "avatars respond invalid data"), null);
        }

        log.info("Found sizes: {}", logos.stream().map(AvatarPicture::getSize).collect(Collectors.joining(",")));

        AvatarPicture logo = logos.get(0);
        String logoId = logo.getFilename();
        long groupId = logo.getGroupId();
        log.debug("Generated logo id: {}, group id: {}", logoId, groupId);
        String publicUrlMds = avatarImageStoreService.getPictureUrl(logo);
        log.debug("Mds logo url: {}", publicUrlMds);

        TurboLogoData logoToSave = new TurboLogoData(request.getHostId(), logoId, groupId, publicUrlMds, DateTime.now(),
                false, uploadLogoResult.getWidth(), uploadLogoResult.getHeight());
        turboLogoYDao.add(logoToSave);
        EnumMap<TurboHostHeaderType, String> logoUrls = new EnumMap<>(TurboHostHeaderType.class);
        if (!uploadLogoResult.isSvg()) {
            logoUrls.put(SQUARE, avatarImageStoreService.getPictureUrl(logo, SQUARE.getLogoSize()));
            logoUrls.put(HORIZONTAL, avatarImageStoreService.getPictureUrl(logo, HORIZONTAL.getLogoSize()));
        }
        // для фронтэнда склеиваем id в один
        return new Response.NormalResponse(logoId + TurboLogoData.FRONT_LOGO_ID_SEPARATOR + groupId,
                logoUrls, uploadLogoResult.isSvg());
    }

    @Getter
    public static final class Request extends AbstractUserVerifiedHostRequest {
        @Description("URL для загрузки логотипа")
        @Setter(onMethod_ = @RequestQueryProperty)
        private String logoUrl;
        @Description("Файл с логотипом")
        @Setter(onMethod_ = @RequestFileProperty)
        private FileParameter logoFile;
    }

    public abstract static class Response implements ActionResponse {

        public enum UploadTurboLogoErrorType {
            UPLOAD_TURBO_LOGO__LOGO_IS_EMPTY,
            UPLOAD_TURBO_LOGO__LOGO_IS_TOO_LONG,
            UPLOAD_TURBO_LOGO__LOGO_IS_TOO_SMALL,
            UPLOAD_TURBO_LOGO__UNABLE_TO_DOWNLOAD_LOGO_BY_URL,
            UPLOAD_TURBO_LOGO__UNSUPPORTED_FORMAT,
        }

        @Value
        public static class NormalResponse extends Response implements ActionResponse.NormalResponse {
            @Description("id логотипа")
            String logoId;
            @Description("Ссылки на логотип в различных вариантах")
            Map<TurboHostHeaderType, String> logoUrls;
            @Description("SVG ли это")
            boolean svg;
        }

        abstract static class BaseErrorResponse extends Response implements ErrorResponse {
            private final Class<?> clazz;

            protected BaseErrorResponse(Class<?> clazz) {
                this.clazz = clazz;
            }

            @Override
            public Class<?> getClazz() {
                return clazz;
            }
        }

        public static class LogoIsEmptyResponse extends Response.BaseErrorResponse {
            public LogoIsEmptyResponse(Class<?> clazz) {
                super(clazz);
            }

            @Override
            public Enum<?> getCode() {
                return UploadTurboLogoErrorType.UPLOAD_TURBO_LOGO__LOGO_IS_EMPTY;
            }

            @Override
            public String getMessage() {
                return "Logo is empty";
            }
        }

        public static class LogoIsTooLongResponse extends Response.BaseErrorResponse {
            private final String message;

            public LogoIsTooLongResponse(Class<?> clazz, String message) {
                super(clazz);
                this.message = message;
            }

            @Override
            public Enum<?> getCode() {
                return UploadTurboLogoErrorType.UPLOAD_TURBO_LOGO__LOGO_IS_TOO_LONG;
            }

            @Override
            public String getMessage() {
                return "Logo is too long: " + StringUtils.trimToEmpty(message);
            }
        }

        public static class LogoIsTooSmallResponse extends Response.BaseErrorResponse {
            public LogoIsTooSmallResponse(Class<?> clazz) {
                super(clazz);
            }

            @Override
            public Enum<?> getCode() {
                return UploadTurboLogoErrorType.UPLOAD_TURBO_LOGO__LOGO_IS_TOO_SMALL;
            }

            @Override
            public String getMessage() {
                return "Logo is too small";
            }
        }

        public static class UnableToDownloadLogoByUrlResponse extends Response.BaseErrorResponse {
            public UnableToDownloadLogoByUrlResponse(Class<?> clazz) {
                super(clazz);
            }

            @Override
            public Enum<?> getCode() {
                return UploadTurboLogoErrorType.UPLOAD_TURBO_LOGO__UNABLE_TO_DOWNLOAD_LOGO_BY_URL;
            }

            @Override
            public String getMessage() {
                return "Unable to download logo by url";
            }
        }

        @Description("Неподдерживаемый формат")
        public static class UnsupportedFormatResponse extends Response.BaseErrorResponse {
            public UnsupportedFormatResponse(Class<?> clazz) {
                super(clazz);
            }

            @Override
            public Enum<?> getCode() {
                return UploadTurboLogoErrorType.UPLOAD_TURBO_LOGO__UNSUPPORTED_FORMAT;
            }

            @Override
            public String getMessage() {
                return "Unsupported file format";
            }
        }

    }

}
