package ch.threema.app.webclient.services.instance.message.receiver;

import org.msgpack.core.MessagePackException;
import org.msgpack.value.Value;
import org.slf4j.Logger;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;

import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringDef;
import androidx.annotation.WorkerThread;
import ch.threema.app.profilepicture.CheckedProfilePicture;
import ch.threema.app.services.UserService;
import ch.threema.app.webclient.Protocol;
import ch.threema.app.webclient.services.instance.MessageDispatcher;
import ch.threema.app.webclient.services.instance.MessageReceiver;
import static ch.threema.base.utils.LoggingKt.getThreemaLogger;
import ch.threema.domain.taskmanager.TriggerSource;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * Process update/profile requests from the browser.
 */
@WorkerThread
public class ModifyProfileHandler extends MessageReceiver {
    private static final Logger logger = getThreemaLogger("ModifyProfileHandler");

    private static final String FIELD_NICKNAME = "publicNickname";
    private static final String FIELD_AVATAR = "avatar";

    // Dispatchers
    @NonNull
    private final MessageDispatcher responseDispatcher;

    // Services
    @NonNull
    private final UserService userService;

    // Error codes
    @Retention(RetentionPolicy.SOURCE)
    @StringDef({
        Protocol.ERROR_INVALID_AVATAR,
        Protocol.ERROR_VALUE_TOO_LONG,
        Protocol.ERROR_INTERNAL,
    })
    private @interface ErrorCode {
    }

    private static class ModifyProfileException extends Exception {
        @ErrorCode
        String errorCode;

        ModifyProfileException(@ErrorCode String errorCode) {
            super();
            this.errorCode = errorCode;
        }
    }

    @AnyThread
    public ModifyProfileHandler(
        @NonNull MessageDispatcher responseDispatcher,
        @NonNull UserService userService
    ) {
        super(Protocol.SUB_TYPE_PROFILE);
        this.responseDispatcher = responseDispatcher;
        this.userService = userService;
    }

    @Override
    protected void receive(Map<String, Value> message) throws MessagePackException {
        logger.debug("Received update profile message");

        // Get data and args
        final Map<String, Value> data = this.getData(message, false);
        final Map<String, Value> args = this.getArguments(message, false);

        // Process args
        if (!args.containsKey(Protocol.ARGUMENT_TEMPORARY_ID)) {
            logger.error("Invalid profile update request, temporaryId not set");
            return;
        }
        final String temporaryId = args.get(Protocol.ARGUMENT_TEMPORARY_ID).asStringValue().toString();

        try {
            if (data.containsKey(FIELD_NICKNAME)) {
                final String nickname = data.get(FIELD_NICKNAME).asStringValue().toString();
                this.processNickname(nickname);
            }

            if (data.containsKey(FIELD_AVATAR)) {
                final Value value = data.get(FIELD_AVATAR);
                if (value == null || value.isNilValue()) {
                    this.processAvatar(null);
                } else {
                    final byte[] avatar = value.asBinaryValue().asByteArray();
                    this.processAvatar(avatar);
                }
            }
        } catch (ModifyProfileException e) {
            logger.error("Profile was not updated ({})", e.errorCode, e);
            this.sendConfirmActionFailure(this.responseDispatcher, temporaryId, e.errorCode);
        }

        logger.debug("Profile was updated");
        this.sendConfirmActionSuccess(this.responseDispatcher, temporaryId);
    }

    /**
     * Update the nickname.
     */
    private void processNickname(String nickname) throws ModifyProfileException {
        if (nickname.getBytes(UTF_8).length > Protocol.LIMIT_BYTES_PUBLIC_NICKNAME) {
            throw new ModifyProfileException(Protocol.ERROR_VALUE_TOO_LONG);
        }
        this.userService.setPublicNickname(nickname, TriggerSource.LOCAL);
    }

    /**
     * Update the avatar.
     */
    private void processAvatar(@Nullable byte[] avatarBytes) throws ModifyProfileException {
        // If avatar bytes are null, delete own avatar.
        if (avatarBytes == null) {
            userService.removeUserProfilePicture(TriggerSource.LOCAL);
            return;
        }

        CheckedProfilePicture profilePicture = CheckedProfilePicture.getOrConvertFromBytes(avatarBytes);
        if (profilePicture != null) {
            userService.setUserProfilePicture(profilePicture, TriggerSource.LOCAL);
        } else {
            logger.warn("Could not set user profile picture as the provided bytes are not valid");
            throw new ModifyProfileException(Protocol.ERROR_INVALID_AVATAR);
        }
    }

    @Override
    protected boolean maybeNeedsConnection() {
        // We don't need to send any messages as a result of modifying the profile.
        return false;
    }
}
