/*
 * Copyright 2019-present HiveMQ GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hivemq.codec.encoder.mqtt5;

import com.google.common.collect.ImmutableList;
import com.hivemq.configuration.service.SecurityConfigurationService;
import com.hivemq.mqtt.message.dropping.MessageDroppedService;
import com.hivemq.mqtt.message.mqtt5.Mqtt5UserProperties;
import com.hivemq.mqtt.message.mqtt5.MqttUserProperty;
import com.hivemq.mqtt.message.pubrec.PUBREC;
import com.hivemq.mqtt.message.reason.Mqtt5PubRecReasonCode;
import com.hivemq.util.ChannelAttributes;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.when;

/**
 * @author Waldemar Ruck
 * @since 4.0
 */
public class Mqtt5PubrecEncoderTest extends AbstractMqtt5EncoderTest {

    private static final Mqtt5PubRecReasonCode SUCCESS = Mqtt5PubRecReasonCode.SUCCESS;

    @Mock
    private MessageDroppedService messageDroppedService;

    @Mock
    private SecurityConfigurationService securityConfigurationService;

    private Mqtt5PubrecEncoder encoder;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        when(securityConfigurationService.allowRequestProblemInformation()).thenReturn(true);

        encoder = new Mqtt5PubrecEncoder(messageDroppedService, securityConfigurationService);
        super.setUp(encoder);
    }

    @Test
    public void encode_all_properties() {
        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                30,
                // variable header
                //   packet identifier
                127, 1,
                //   PUBREC reason code
                (byte) SUCCESS.getCode(),
                //   properties length
                26,
                //   properties
                //     reason string
                0x1F, 0, 6, 'r', 'e', 'a', 's', 'o', 'n',
                //     user property
                0x26, 0, 4, 'u', 's', 'e', 'r', 0, 8, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y'
        };

        final MqttUserProperty userProperty = new MqttUserProperty("user", "property");
        final Mqtt5UserProperties userProperties =
                Mqtt5UserProperties.of(ImmutableList.of(userProperty));

        final PUBREC pubRec =
                new PUBREC((127 * 256) + 1, SUCCESS, "reason", userProperties);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_simple_reason_string() {
        // MQTT v5.0 Spec §3.4.2.2
        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                13,
                // variable header
                //   packet identifier
                0, 1,
                //   PUBREC reason code
                (byte) SUCCESS.getCode(),
                //   property length
                9,
                //   properties
                //     reason string
                0x1F, 0, 6, 'r', 'e', 'a', 's', 'o', 'n'
        };

        final PUBREC pubRec = new PUBREC(1, SUCCESS, "reason", Mqtt5UserProperties.NO_USER_PROPERTIES);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_simple_user_property() {
        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                21,
                // variable header
                //   packet identifier
                0, 1,
                //   PUBREC reason code
                (byte) SUCCESS.getCode(),
                //   property length
                17,
                //   properties
                //     user property
                0x26, 0, 4, 'u', 's', 'e', 'r', 0, 8, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y'
        };

        final MqttUserProperty userProperty = new MqttUserProperty("user", "property");
        final Mqtt5UserProperties userProperties =
                Mqtt5UserProperties.of(ImmutableList.of(userProperty));

        final PUBREC pubRec = new PUBREC(1, SUCCESS, null, userProperties);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_reason_string_request_problem_information_false() {

        when(securityConfigurationService.allowRequestProblemInformation()).thenReturn(false);

        // MQTT v5.0 Spec §3.4.2.2
        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                2,
                // variable header
                //   packet identifier
                0, 1
        };

        final PUBREC pubRec = new PUBREC(1, SUCCESS, "reason", Mqtt5UserProperties.NO_USER_PROPERTIES);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_user_property_request_problem_information_false() {

        when(securityConfigurationService.allowRequestProblemInformation()).thenReturn(true);
        channel.attr(ChannelAttributes.REQUEST_PROBLEM_INFORMATION).set(false);

        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                2,
                // variable header
                //   packet identifier
                0, 1
        };

        final MqttUserProperty userProperty = new MqttUserProperty("user", "property");
        final Mqtt5UserProperties userProperties =
                Mqtt5UserProperties.of(ImmutableList.of(userProperty));

        final PUBREC pubRec = new PUBREC(1, SUCCESS, null, userProperties);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_reason_string_and_user_property_request_problem_information_false() {

        when(securityConfigurationService.allowRequestProblemInformation()).thenReturn(true);
        channel.attr(ChannelAttributes.REQUEST_PROBLEM_INFORMATION).set(false);

        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                3,
                // variable header
                //   packet identifier
                0, 1,
                //   PUBREC reason code
                (byte) Mqtt5PubRecReasonCode.NOT_AUTHORIZED.getCode()
        };

        final MqttUserProperty userProperty = new MqttUserProperty("user", "property");
        final Mqtt5UserProperties userProperties =
                Mqtt5UserProperties.of(ImmutableList.of(userProperty));

        final PUBREC pubRec = new PUBREC(1, Mqtt5PubRecReasonCode.NOT_AUTHORIZED, "reason", userProperties);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_omit_reason_code_success() {
        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                2,
                // variable header
                //   packet identifier
                0, 1
        };

        final PUBREC pubRec = new PUBREC(1, Mqtt5PubRecReasonCode.SUCCESS, null, Mqtt5UserProperties.NO_USER_PROPERTIES);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_reason_string_empty() {
        final byte[] expected = {
                // fixed header
                //   type, flags
                (byte) 0b0101_0000,
                //   remaining length
                24,
                // variable header
                //   packet identifier
                0, 1,
                //   reason code (continue)
                (byte) SUCCESS.getCode(),
                //   properties
                20,
                //     reason string
                0x1F, 0, 0,
                0x26, 0, 4, 'u', 's', 'e', 'r', 0, 8, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y'
        };

        final MqttUserProperty userProperty = new MqttUserProperty("user", "property");
        final Mqtt5UserProperties userProperties =
                Mqtt5UserProperties.of(ImmutableList.of(userProperty));

        final PUBREC pubRec = new PUBREC(1, Mqtt5PubRecReasonCode.SUCCESS, "", userProperties);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_do_not_omit_non_success_reason_codes() {

        final Mqtt5PubRecReasonCode notAuthorizedCode = Mqtt5PubRecReasonCode.NOT_AUTHORIZED;

        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                3,
                // variable header
                //   packet identifier
                0, 1,
                //   PUBREC reason code
                (byte) notAuthorizedCode.getCode()
        };

        final PUBREC pubRec = new PUBREC(1, notAuthorizedCode, null, Mqtt5UserProperties.NO_USER_PROPERTIES);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_multiple_user_properties() {
        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                48,
                // variable header
                //   packet identifier
                0, 1,
                //   PUBREC reason code
                (byte) SUCCESS.getCode(),
                //   property length
                44,
                //   properties
                //     reason string
                0x1F, 0, 6, 'r', 'e', 'a', 's', 'o', 'n',
                //     user property
                0x26, 0, 4, 'u', 's', 'e', 'r', 0, 8, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y',
                //     user property
                0x26, 0, 4, 'u', 's', 'e', 'r', 0, 9, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y', '2'
        };

        final MqttUserProperty userProperty = new MqttUserProperty("user", "property");
        final MqttUserProperty userProperty2 = new MqttUserProperty("user", "property2");
        final Mqtt5UserProperties userProperties =
                Mqtt5UserProperties.of(ImmutableList.of(userProperty, userProperty2));

        final PUBREC pubRec = new PUBREC(1, SUCCESS, "reason", userProperties);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_maximum_packet_size_exceeded_on_success_omit_user_properties() {
        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                2,
                // variable header
                //   packet identifier
                0, 1
        };
        final MaximumPacketBuilder maxPacket = new MaximumPacketBuilder().build(MAX_PACKET_SIZE);

        final PUBREC pubRec = new PUBREC(1, SUCCESS, null, getUserProperties(maxPacket.getMaxUserPropertiesCount() + 1));
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

    @Test
    public void encode_omit_reason_code_and_property_length() {
        // MQTT v5.0 Spec §3.4.2.1
        final byte[] expected = {
                // fixed header
                //   type, reserved
                (byte) 0b0101_0000,
                //   remaining length
                2,
                // variable header
                //   packet identifier
                0, 1
        };

        final Mqtt5UserProperties userProperties = Mqtt5UserProperties.NO_USER_PROPERTIES;

        final PUBREC pubRec = new PUBREC(1, SUCCESS, null, userProperties);
        encodeTestBufferSize(expected, pubRec, encoder.bufferSize(channel.pipeline().context(encoder), pubRec));
    }

}