// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.messaging.servicebus.implementation;

import com.azure.messaging.servicebus.TestUtils;
import com.azure.messaging.servicebus.administration.implementation.EntityHelper;
import com.azure.messaging.servicebus.administration.implementation.ServiceBusManagementSerializer;
import com.azure.messaging.servicebus.administration.implementation.models.AuthorizationRuleImpl;
import com.azure.messaging.servicebus.administration.implementation.models.CorrelationFilterImpl;
import com.azure.messaging.servicebus.administration.implementation.models.EmptyRuleActionImpl;
import com.azure.messaging.servicebus.administration.implementation.models.FalseFilterImpl;
import com.azure.messaging.servicebus.administration.implementation.models.KeyValueImpl;
import com.azure.messaging.servicebus.administration.implementation.models.MessageCountDetailsImpl;
import com.azure.messaging.servicebus.administration.implementation.models.NamespacePropertiesEntryImpl;
import com.azure.messaging.servicebus.administration.implementation.models.QueueDescriptionEntryContentImpl;
import com.azure.messaging.servicebus.administration.implementation.models.QueueDescriptionEntryImpl;
import com.azure.messaging.servicebus.administration.implementation.models.QueueDescriptionFeedImpl;
import com.azure.messaging.servicebus.administration.implementation.models.QueueDescriptionImpl;
import com.azure.messaging.servicebus.administration.implementation.models.ResponseAuthorImpl;
import com.azure.messaging.servicebus.administration.implementation.models.ResponseLinkImpl;
import com.azure.messaging.servicebus.administration.implementation.models.RuleDescriptionEntryContentImpl;
import com.azure.messaging.servicebus.administration.implementation.models.RuleDescriptionEntryImpl;
import com.azure.messaging.servicebus.administration.implementation.models.RuleDescriptionFeedImpl;
import com.azure.messaging.servicebus.administration.implementation.models.RuleDescriptionImpl;
import com.azure.messaging.servicebus.administration.implementation.models.SqlFilterImpl;
import com.azure.messaging.servicebus.administration.implementation.models.SqlRuleActionImpl;
import com.azure.messaging.servicebus.administration.implementation.models.SubscriptionDescriptionEntryContentImpl;
import com.azure.messaging.servicebus.administration.implementation.models.SubscriptionDescriptionEntryImpl;
import com.azure.messaging.servicebus.administration.implementation.models.SubscriptionDescriptionFeedImpl;
import com.azure.messaging.servicebus.administration.implementation.models.SubscriptionDescriptionImpl;
import com.azure.messaging.servicebus.administration.implementation.models.TitleImpl;
import com.azure.messaging.servicebus.administration.implementation.models.TrueFilterImpl;
import com.azure.messaging.servicebus.administration.models.AccessRights;
import com.azure.messaging.servicebus.administration.models.AuthorizationRule;
import com.azure.messaging.servicebus.administration.models.CreateQueueOptions;
import com.azure.messaging.servicebus.administration.models.CreateSubscriptionOptions;
import com.azure.messaging.servicebus.administration.models.EntityStatus;
import com.azure.messaging.servicebus.administration.models.MessagingSku;
import com.azure.messaging.servicebus.administration.models.NamespaceProperties;
import com.azure.messaging.servicebus.administration.models.NamespaceType;
import com.azure.messaging.servicebus.administration.models.QueueProperties;
import com.azure.messaging.servicebus.administration.models.QueueRuntimeProperties;
import com.azure.messaging.servicebus.administration.models.SharedAccessAuthorizationRule;
import com.azure.messaging.servicebus.administration.models.SubscriptionRuntimeProperties;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

class ServiceBusManagementSerializerTest {
    private static final ServiceBusManagementSerializer SERIALIZER = new ServiceBusManagementSerializer();

    /**
     * Verify we can deserialize XML request when creating a queue.
     */
    @Test
    void deserializeCreateQueueDescription() throws IOException {
        // Arrange
        final String contents = getContents("CreateQueueEntry.xml");
        final AuthorizationRule rule = new SharedAccessAuthorizationRule("test-name",
            "fakePrimaryKey",
            "fakeSecondaryKey",
            Collections.singletonList(AccessRights.SEND));
        final CreateQueueOptions expected = new CreateQueueOptions()
            .setAutoDeleteOnIdle(null)
            .setDefaultMessageTimeToLive(null)
            .setDuplicateDetectionHistoryTimeWindow(null)
            .setLockDuration(Duration.ofMinutes(10))
            .setMaxSizeInMegabytes(1028)
            .setDuplicateDetectionRequired(false)
            .setSessionRequired(true)
            .setDeadLetteringOnMessageExpiration(false)
            .setMaxDeliveryCount(5)
            .setBatchedOperationsEnabled(true)
            .setPartitioningEnabled(false);

        expected.getAuthorizationRules().add(rule);

        // Act
        final QueueDescriptionEntryImpl entry = SERIALIZER.deserialize(contents, QueueDescriptionEntryImpl.class);

        // Assert
        assertNotNull(entry);
        assertNotNull(entry.getContent());

        final QueueDescriptionImpl actual = entry.getContent().getQueueDescription();
        assertQueueEquals(expected, EntityStatus.ACTIVE, actual);

        final List<AuthorizationRule> actualRules = actual.getAuthorizationRules().stream()
            .map(TestAuthorizationRule::new)
            .collect(Collectors.toList());
        TestUtils.assertAuthorizationRules(expected.getAuthorizationRules(), actualRules);
    }

    /**
     * Verify we can deserialize XML from a GET queue request.
     */
    @Test
    void deserializeQueueDescription() throws IOException {
        // Arrange
        final String contents = getContents("QueueDescriptionEntry.xml");
        final String queueName = "my-test-queue";
        final CreateQueueOptions expected = new CreateQueueOptions()
            .setLockDuration(Duration.ofMinutes(5))
            .setMaxSizeInMegabytes(1024)
            .setDuplicateDetectionRequired(true)
            .setSessionRequired(true)
            .setDefaultMessageTimeToLive(Duration.parse("PT3H20M10S"))
            .setDeadLetteringOnMessageExpiration(false)
            .setDuplicateDetectionHistoryTimeWindow(Duration.ofMinutes(10))
            .setMaxDeliveryCount(10)
            .setBatchedOperationsEnabled(true)
            .setAutoDeleteOnIdle(Duration.ofHours(5))
            .setPartitioningEnabled(true);

        // Act
        final QueueDescriptionEntryImpl entry = SERIALIZER.deserialize(contents, QueueDescriptionEntryImpl.class);

        // Assert
        assertNotNull(entry);
        assertNotNull(entry.getContent());

        // The entry title is the name of the queue.
        assertTitle(queueName, entry.getTitle());

        final QueueDescriptionImpl actual = entry.getContent().getQueueDescription();
        assertQueueEquals(expected, EntityStatus.DELETING, actual);
    }

    /**
     * Verify we can deserialize XML from a GET queue request and create convenience model, {@link QueueRuntimeProperties}.
     */
    @Test
    void deserializeQueueRuntimeProperties() throws IOException {
        final String contents = getContents("QueueDescriptionEntry.xml");

        final OffsetDateTime createdAt = OffsetDateTime.parse("2020-06-05T03:55:07.5Z");
        final OffsetDateTime updatedAt = OffsetDateTime.parse("2020-06-05T03:45:07.64Z");
        final OffsetDateTime accessedAt = OffsetDateTime.parse("0001-01-01T00:00:00Z");
        final int sizeInBytes = 2048;
        final int messageCount = 23;
        final MessageCountDetailsImpl expectedCount = new MessageCountDetailsImpl()
            .setActiveMessageCount(5)
            .setDeadLetterMessageCount(3)
            .setScheduledMessageCount(65)
            .setTransferMessageCount(10)
            .setTransferDeadLetterMessageCount(123);

        // Act
        final QueueDescriptionEntryImpl entry = SERIALIZER.deserialize(contents, QueueDescriptionEntryImpl.class);
        final QueueProperties properties = EntityHelper.toModel(entry.getContent().getQueueDescription());
        final QueueRuntimeProperties actual = new QueueRuntimeProperties(properties);

        // Assert
        assertEquals(sizeInBytes, actual.getSizeInBytes());
        assertEquals(messageCount, actual.getTotalMessageCount());

        assertEquals(createdAt, actual.getCreatedAt());
        assertEquals(updatedAt, actual.getUpdatedAt());
        assertEquals(accessedAt, actual.getAccessedAt());

        assertEquals(expectedCount.getActiveMessageCount(), actual.getActiveMessageCount());
        assertEquals(expectedCount.getDeadLetterMessageCount(), actual.getDeadLetterMessageCount());
        assertEquals(expectedCount.getScheduledMessageCount(), actual.getScheduledMessageCount());
        assertEquals(expectedCount.getTransferMessageCount(), actual.getTransferMessageCount());
        assertEquals(expectedCount.getTransferDeadLetterMessageCount(), actual.getTransferDeadLetterMessageCount());
    }

    /**
     * Verify we can deserialize feed XML from a list queues operation.  This caused errors in previous versions of
     * jackson 2.10.0 because {@link QueueDescriptionEntryImpl#getTitle()} is a complex object.
     */
    @Test
    void deserializeQueueDescriptionFeedDoesNotError() throws IOException {
        // Arrange
        final String contents = getContents("QueueDescriptionFeed-Errors.xml");

        // Act
        final QueueDescriptionFeedImpl actual = SERIALIZER.deserialize(contents, QueueDescriptionFeedImpl.class);

        // Assert
        assertNotNull(actual);
        assertEquals(53, actual.getEntry().size());
    }

    /**
     * Verify we can deserialize feed XML from a list queues operation that has a paged response.
     */
    @Test
    void deserializeQueueDescriptionFeedPaged() throws IOException {
        final String contents = getContents("QueueDescriptionFeed-Paged.xml");
        final List<ResponseLinkImpl> responseLinks = Arrays.asList(
            new ResponseLinkImpl().setRel("self")
                .setHref("https://sb-java.servicebus.windows.net/$Resources/queues?api-version=2021-05&enrich=false&$skip=0&$top=5"),
            new ResponseLinkImpl().setRel("next")
                .setHref("https://sb-java.servicebus.windows.net/$Resources/queues?api-version=2021-05&enrich=false&%24skip=5&%24top=5")
        );

        final String queueName = "q-0";
        final CreateQueueOptions options = new CreateQueueOptions()
            .setLockDuration(Duration.ofMinutes(10))
            .setMaxSizeInMegabytes(102)
            .setDuplicateDetectionRequired(true)
            .setSessionRequired(true)
            .setDefaultMessageTimeToLive(Duration.ofSeconds(10))
            .setDeadLetteringOnMessageExpiration(false)
            .setDuplicateDetectionHistoryTimeWindow(Duration.ofMinutes(10))
            .setMaxDeliveryCount(10)
            .setBatchedOperationsEnabled(true)
            .setAutoDeleteOnIdle(Duration.ofSeconds(5))
            .setPartitioningEnabled(true);
        final QueueDescriptionImpl queueProperties = EntityHelper.getQueueDescription(options);

        final QueueDescriptionEntryImpl entry1 = new QueueDescriptionEntryImpl()
            .setBase("https://sb-java.servicebus.windows.net/$Resources/queues?api-version=2021-05&enrich=false&$skip=0&$top=5")
            .setId("https://sb-java.servicebus.windows.net/q-0?api-version=2021-05")
            .setTitle(getResponseTitle("q-0"))
            .setPublished(OffsetDateTime.parse("2020-03-05T07:17:04Z"))
            .setUpdated(OffsetDateTime.parse("2020-01-05T07:17:04Z"))
            .setAuthor(new ResponseAuthorImpl().setName("sb-java"))
            .setLink(new ResponseLinkImpl().setRel("self").setHref("../q-0?api-version=2021-05"))
            .setContent(new QueueDescriptionEntryContentImpl().setType("application/xml")
                .setQueueDescription(queueProperties));
        final QueueDescriptionEntryImpl entry2 = new QueueDescriptionEntryImpl()
            .setBase("https://sb-java.servicebus.windows.net/$Resources/queues?api-version=2021-05&enrich=false&$skip=0&$top=5")
            .setId("https://sb-java.servicebus.windows.net/q-1?api-version=2021-05")
            .setTitle(getResponseTitle("q-1"))
            .setPublished(OffsetDateTime.parse("2020-06-10T07:16:25Z"))
            .setUpdated(OffsetDateTime.parse("2020-06-15T07:16:25Z"))
            .setAuthor(new ResponseAuthorImpl().setName("sb-java2"))
            .setLink(new ResponseLinkImpl().setRel("self").setHref("../q-1?api-version=2021-05"))
            .setContent(new QueueDescriptionEntryContentImpl().setType("application/xml")
                .setQueueDescription(queueProperties));
        final QueueDescriptionEntryImpl entry3 = new QueueDescriptionEntryImpl()
            .setBase("https://sb-java.servicebus.windows.net/$Resources/queues?api-version=2021-05&enrich=false&$skip=0&$top=5")
            .setId("https://sb-java.servicebus.windows.net/q-2?api-version=2021-05")
            .setTitle(getResponseTitle("q-2"))
            .setPublished(OffsetDateTime.parse("2020-06-05T07:17:06Z"))
            .setUpdated(OffsetDateTime.parse("2020-06-05T07:17:06Z"))
            .setAuthor(new ResponseAuthorImpl().setName("sb-java3"))
            .setLink(new ResponseLinkImpl().setRel("self").setHref("../q-2?api-version=2021-05"))
            .setContent(new QueueDescriptionEntryContentImpl().setType("application/xml")
                .setQueueDescription(queueProperties));

        final List<QueueDescriptionEntryImpl> entries = Arrays.asList(entry1, entry2, entry3);
        final QueueDescriptionFeedImpl expected = new QueueDescriptionFeedImpl()
            .setId("feed-id")
            .setTitle(new TitleImpl().setType("type").setContent("Queues"))
            .setUpdated(OffsetDateTime.parse("2020-12-05T07:17:21Z"))
            .setLink(responseLinks)
            .setEntry(entries);

        // Act
        final QueueDescriptionFeedImpl actual = SERIALIZER.deserialize(contents, QueueDescriptionFeedImpl.class);

        // Assert
        assertEquals(expected.getId(), actual.getId());
        assertEquals(expected.getTitle().getContent(), actual.getTitle().getContent());
        assertEquals(expected.getUpdated(), actual.getUpdated());

        assertNotNull(actual.getLink());
        assertEquals(expected.getLink().size(), actual.getLink().size());
        for (int i = 0; i < expected.getLink().size(); i++) {
            final ResponseLinkImpl expectedLink = expected.getLink().get(i);
            final ResponseLinkImpl actualLink = actual.getLink().get(i);

            assertEquals(expectedLink.getRel(), actualLink.getRel());
            assertEquals(expectedLink.getHref(), actualLink.getHref());
        }

        assertNotNull(actual.getEntry());
        assertEquals(expected.getEntry().size(), actual.getEntry().size());
        for (int i = 0; i < expected.getEntry().size(); i++) {
            final QueueDescriptionEntryImpl expectedEntry = expected.getEntry().get(i);
            final QueueDescriptionEntryImpl actualEntry = actual.getEntry().get(i);

            assertEquals(expected.getId(), actual.getId());
            assertNotNull(actual.getTitle());

            assertEquals(expectedEntry.getTitle().getContent(), actualEntry.getTitle().getContent());
            assertEquals(expectedEntry.getUpdated(), actualEntry.getUpdated());
            assertEquals(expectedEntry.getPublished(), actualEntry.getPublished());
            assertEquals(expectedEntry.getAuthor().getName(), actualEntry.getAuthor().getName());

            assertQueueEquals(options, EntityStatus.ACTIVE, actualEntry.getContent().getQueueDescription());
        }
    }

    /**
     * Verify we can deserialize XML from a GET namespace request.
     */
    @Test
    void deserializeNamespace() throws IOException {
        // Arrange
        final String contents = getContents("NamespaceEntry.xml");
        final String name = "ShivangiServiceBus";
        final String alias = "MyServiceBusFallback";
        final OffsetDateTime createdTime = OffsetDateTime.parse("2020-04-09T08:38:55.807Z");
        final OffsetDateTime modifiedTime = OffsetDateTime.parse("2020-06-12T06:34:38.383Z");
        final MessagingSku sku = MessagingSku.PREMIUM;
        final NamespaceType namespaceType = NamespaceType.MESSAGING;

        // Act
        final NamespacePropertiesEntryImpl entry = SERIALIZER.deserialize(contents, NamespacePropertiesEntryImpl.class);

        // Assert
        assertNotNull(entry);
        assertNotNull(entry.getContent());

        // The entry title is the name of the service bus instance.
        assertTitle(name, entry.getTitle());

        final NamespaceProperties actual = entry.getContent().getNamespaceProperties();
        assertEquals(name, actual.getName());
        assertEquals(alias, actual.getAlias());
        assertEquals(createdTime, actual.getCreatedTime());
        assertEquals(modifiedTime, actual.getModifiedTime());
        assertEquals(sku, actual.getMessagingSku());
        assertEquals(namespaceType, actual.getNamespaceType());
    }

    /**
     * Verify we can deserialize XML from a GET subscription request.
     */
    @Test
    void deserializeSubscription() throws IOException {
        // Arrange
        final String contents = getContents("SubscriptionDescriptionEntry.xml");
        final SubscriptionDescriptionImpl expected = new SubscriptionDescriptionImpl()
            .setLockDuration(Duration.ofSeconds(15))
            .setRequiresSession(true)
            .setDefaultMessageTimeToLive(ServiceBusConstants.MAX_DURATION)
            .setDeadLetteringOnMessageExpiration(false)
            .setDeadLetteringOnFilterEvaluationExceptions(true)
            .setEnableBatchedOperations(true)
            .setMaxDeliveryCount(5)
            .setAutoDeleteOnIdle(Duration.ofHours(1).plusMinutes(48));

        // Act
        final SubscriptionDescriptionEntryImpl entry = SERIALIZER.deserialize(contents, SubscriptionDescriptionEntryImpl.class);

        // Assert
        assertNotNull(entry);
        assertNotNull(entry.getContent());

        final SubscriptionDescriptionImpl actual = entry.getContent().getSubscriptionDescription();

        assertSubscriptionEquals(expected, EntityStatus.ACTIVE, actual);
    }

    /**
     * Verify we can deserialize XML from a PUT subscription request.
     */
    @Test
    void deserializeCreateSubscription() throws IOException {
        // Arrange
        final String contents = getContents("CreateSubscriptionEntry.xml");
        final String topicName = "topic";
        final String subscriptionName = "sub46850f";
        final SubscriptionDescriptionImpl expected = EntityHelper.getSubscriptionDescription(
            new CreateSubscriptionOptions()
                .setAutoDeleteOnIdle(Duration.parse("P10675199DT2H48M5.477S"))
                .setDefaultMessageTimeToLive(Duration.parse("P10675199DT2H48M5.477S"))
                .setSessionRequired(false)
                .setLockDuration(Duration.ofSeconds(45))
                .setMaxDeliveryCount(7));

        // Act
        final SubscriptionDescriptionEntryImpl entry = SERIALIZER.deserialize(contents, SubscriptionDescriptionEntryImpl.class);

        // Assert
        assertNotNull(entry);
        assertNotNull(entry.getContent());

        final SubscriptionDescriptionImpl actual = entry.getContent().getSubscriptionDescription();

        assertSubscriptionEquals(expected, EntityStatus.ACTIVE, actual);
    }

    /**
     * Verify we can deserialize XML from a GET subscription request and create convenience model, {@link
     * SubscriptionRuntimeProperties}.
     */
    @Test
    void deserializeSubscriptionRuntimeProperties() throws IOException {
        final String contents = getContents("SubscriptionDescriptionEntry.xml");

        final OffsetDateTime createdAt = OffsetDateTime.parse("2020-06-22T23:47:54.0131447Z");
        final OffsetDateTime updatedAt = OffsetDateTime.parse("2020-06-22T23:47:20.0131447Z");
        final OffsetDateTime accessedAt = OffsetDateTime.parse("2020-06-22T23:47:54.013Z");
        final int messageCount = 13;
        final MessageCountDetailsImpl expectedCount = new MessageCountDetailsImpl()
            .setActiveMessageCount(10)
            .setDeadLetterMessageCount(50)
            .setScheduledMessageCount(34)
            .setTransferMessageCount(11)
            .setTransferDeadLetterMessageCount(2);

        // Act
        final SubscriptionDescriptionEntryImpl entry = SERIALIZER.deserialize(contents, SubscriptionDescriptionEntryImpl.class);
        final SubscriptionRuntimeProperties actual = new SubscriptionRuntimeProperties(
            EntityHelper.toModel(entry.getContent().getSubscriptionDescription()));

        // Assert
        assertEquals(messageCount, actual.getTotalMessageCount());

        assertEquals(createdAt, actual.getCreatedAt());
        assertEquals(updatedAt, actual.getUpdatedAt());
        assertEquals(accessedAt, actual.getAccessedAt());

        assertEquals(expectedCount.getActiveMessageCount(), actual.getActiveMessageCount());
        assertEquals(expectedCount.getDeadLetterMessageCount(), actual.getDeadLetterMessageCount());
        assertEquals(expectedCount.getTransferMessageCount(), actual.getTransferMessageCount());
        assertEquals(expectedCount.getTransferDeadLetterMessageCount(), actual.getTransferDeadLetterMessageCount());
    }

    /**
     * Verify we can deserialize feed XML from a list of subscriptions that has a paged response.
     */
    @Test
    void deserializeSubscriptionDescriptionFeed() throws IOException {
        // Arrange
        final String contents = getContents("SubscriptionDescriptionFeed.xml");
        final List<ResponseLinkImpl> responseLinks = Collections.singletonList(
            new ResponseLinkImpl().setRel("self")
                .setHref("https://sb-java-conniey-5.servicebus.windows.net/topic/Subscriptions?api-version=2021-05&enrich=false&$skip=0&$top=100")
        );

        final String topicName = "topic";
        final String subscriptionName1 = "subscription-0";
        final String subscriptionName2 = "subscription-session-0";
        final String subscriptionName3 = "subscription-session-1";

        final SubscriptionDescriptionImpl subscription1 = EntityHelper.getSubscriptionDescription(
            new CreateSubscriptionOptions()
                .setLockDuration(Duration.ofSeconds(15))
                .setDefaultMessageTimeToLive(Duration.ofMinutes(5))
                .setMaxDeliveryCount(5)
                .setAutoDeleteOnIdle(Duration.ofDays(1)));
        final SubscriptionDescriptionImpl subscription2 = EntityHelper.getSubscriptionDescription(
            new CreateSubscriptionOptions()
                .setSessionRequired(true)
                .setLockDuration(Duration.ofSeconds(15))
                .setMaxDeliveryCount(5));
        final SubscriptionDescriptionImpl subscription3 = EntityHelper.getSubscriptionDescription(
            new CreateSubscriptionOptions()
                .setSessionRequired(true)
                .setLockDuration(Duration.ofSeconds(15))
                .setMaxDeliveryCount(5));
        final List<SubscriptionDescriptionImpl> expectedDescriptions = Arrays.asList(
            subscription1, subscription2, subscription3);

        final SubscriptionDescriptionEntryImpl entry1 = new SubscriptionDescriptionEntryImpl()
            .setId("https://sb-java-conniey-5.servicebus.windows.net/topic/Subscriptions/subscription-0?api-version=2021-05")
            .setTitle(getResponseTitle(subscriptionName1))
            .setPublished(OffsetDateTime.parse("2020-06-22T23:47:53Z"))
            .setUpdated(OffsetDateTime.parse("2020-06-23T23:47:53Z"))
            .setLink(new ResponseLinkImpl().setRel("self").setHref("Subscriptions/subscription-0?api-version=2021-05"))
            .setContent(new SubscriptionDescriptionEntryContentImpl()
                .setType("application/xml")
                .setSubscriptionDescription(subscription1));
        final SubscriptionDescriptionEntryImpl entry2 = new SubscriptionDescriptionEntryImpl()
            .setId("https://sb-java-conniey-5.servicebus.windows.net/topic/Subscriptions/subscription-session-0?api-version=2021-05")
            .setTitle(getResponseTitle(subscriptionName2))
            .setPublished(OffsetDateTime.parse("2020-06-22T23:47:53Z"))
            .setUpdated(OffsetDateTime.parse("2020-05-22T23:47:53Z"))
            .setLink(new ResponseLinkImpl().setRel("self").setHref("Subscriptions/subscription-session-0?api-version=2021-05"))
            .setContent(new SubscriptionDescriptionEntryContentImpl()
                .setType("application/xml")
                .setSubscriptionDescription(subscription2));
        final SubscriptionDescriptionEntryImpl entry3 = new SubscriptionDescriptionEntryImpl()
            .setId("https://sb-java-conniey-5.servicebus.windows.net/topic/Subscriptions/subscription-session-1?api-version=2021-05")
            .setTitle(getResponseTitle(subscriptionName3))
            .setPublished(OffsetDateTime.parse("2020-06-22T23:47:54Z"))
            .setUpdated(OffsetDateTime.parse("2020-04-22T23:47:54Z"))
            .setLink(new ResponseLinkImpl().setRel("self").setHref("Subscriptions/subscription-session-1?api-version=2021-05"))
            .setContent(new SubscriptionDescriptionEntryContentImpl()
                .setType("application/xml")
                .setSubscriptionDescription(subscription3));

        final List<SubscriptionDescriptionEntryImpl> entries = Arrays.asList(entry1, entry2, entry3);
        final SubscriptionDescriptionFeedImpl expected = new SubscriptionDescriptionFeedImpl()
            .setId("feed-id")
            .setTitle(new TitleImpl().setType("text").setContent("Subscriptions"))
            .setUpdated(OffsetDateTime.parse("2020-06-30T11:41:32Z"))
            .setLink(responseLinks)
            .setEntry(entries);
        final int expectedNumberOfEntries = 11;

        // Act
        final SubscriptionDescriptionFeedImpl actual = SERIALIZER.deserialize(contents, SubscriptionDescriptionFeedImpl.class);

        // Assert
        assertEquals(expected.getId(), actual.getId());
        assertEquals(expected.getTitle().getContent(), actual.getTitle().getContent());
        assertEquals(expected.getUpdated(), actual.getUpdated());

        assertNotNull(actual.getLink());
        assertEquals(expected.getLink().size(), actual.getLink().size());
        for (int i = 0; i < expected.getLink().size(); i++) {
            final ResponseLinkImpl expectedLink = expected.getLink().get(i);
            final ResponseLinkImpl actualLink = actual.getLink().get(i);

            assertEquals(expectedLink.getRel(), actualLink.getRel());
            assertEquals(expectedLink.getHref(), actualLink.getHref());
        }

        assertNotNull(actual.getEntry());

        // We're not going to assert all 13 entries.
        assertTrue(expected.getEntry().size() < actual.getEntry().size());
        assertEquals(expectedNumberOfEntries, actual.getEntry().size());

        for (int i = 0; i < expected.getEntry().size(); i++) {
            final SubscriptionDescriptionEntryImpl expectedEntry = expected.getEntry().get(i);
            final SubscriptionDescriptionEntryImpl actualEntry = actual.getEntry().get(i);

            assertEquals(expected.getId(), actual.getId());
            assertNotNull(actual.getTitle());

            assertResponseTitle(expectedEntry.getTitle(), actualEntry.getTitle());
            assertEquals(expectedEntry.getUpdated(), actualEntry.getUpdated());
            assertEquals(expectedEntry.getPublished(), actualEntry.getPublished());

            final SubscriptionDescriptionImpl expectedSubscription = expectedDescriptions.get(i);
            assertSubscriptionEquals(expectedSubscription, EntityStatus.ACTIVE,
                actualEntry.getContent().getSubscriptionDescription());
        }
    }

    /**
     * Verify we can deserialize XML from a GET rule.
     */
    @Test
    void deserializeSqlRule() throws IOException {
        // Arrange
        final String contents = getContents("SqlRuleFilter.xml");
        final RuleDescriptionImpl expectedRule = new RuleDescriptionImpl()
            .setName("foo")
            .setCreatedAt(OffsetDateTime.parse("2020-08-28T04:32:20.9387321Z"))
            .setAction(new EmptyRuleActionImpl())
            .setFilter(new SqlFilterImpl()
                .setCompatibilityLevel("20")
                .setSqlExpression("type = \"TestType\""));
        final RuleDescriptionEntryImpl expected = new RuleDescriptionEntryImpl()
            .setId("sb://sb-java-conniey-3.servicebus.windows.net/topic-10/Subscriptions/subscription/Rules/foo?api-version=2021-05&enrich=false")
            .setPublished(OffsetDateTime.parse("2020-08-28T04:32:20Z"))
            .setUpdated(OffsetDateTime.parse("2020-08-28T04:34:20Z"))
            .setContent(new RuleDescriptionEntryContentImpl()
                .setRuleDescription(expectedRule)
                .setType("application/xml"));

        // Act
        final RuleDescriptionEntryImpl actual = SERIALIZER.deserialize(contents, RuleDescriptionEntryImpl.class);

        // Assert
        assertRuleEntryEquals(expected, actual);
    }

    /**
     * Verify we can deserialize XML from a GET rule that includes an action.
     */
    @Test
    void deserializeSqlRuleWithAction() throws IOException {
        // Arrange
        final String contents = getContents("SqlRuleFilterWithAction.xml");
        final RuleDescriptionImpl expectedRule = new RuleDescriptionImpl()
            .setName("foo")
            .setCreatedAt(OffsetDateTime.parse("2020-08-28T04:51:24.9967451Z"))
            .setAction(new SqlRuleActionImpl()
                .setCompatibilityLevel("20")
                .setSqlExpression("set FilterTag = 'true'"))
            .setFilter(new SqlFilterImpl()
                .setCompatibilityLevel("20")
                .setSqlExpression("type = \"TestType\""));
        final RuleDescriptionEntryImpl expected = new RuleDescriptionEntryImpl()
            .setId("https://sb-java-conniey-3.servicebus.windows.net/topic-10/Subscriptions/subscription/Rules/foo?api-version=2021-05")
            .setPublished(OffsetDateTime.parse("2020-08-28T04:51:24Z"))
            .setUpdated(OffsetDateTime.parse("2020-08-28T04:54:24Z"))
            .setContent(new RuleDescriptionEntryContentImpl()
                .setRuleDescription(expectedRule)
                .setType("application/xml"));

        // Act
        final RuleDescriptionEntryImpl actual = SERIALIZER.deserialize(contents, RuleDescriptionEntryImpl.class);

        // Assert
        assertRuleEntryEquals(expected, actual);
    }

    /**
     * Verify we can deserialize XML from a GET correlation filter rule that includes an action.
     */
    @Test
    void deserializeCorrelationFilterRule() throws IOException {
        // Arrange
        final String contents = getContents("CorrelationRuleFilter.xml");
        final RuleDescriptionImpl expectedRule = new RuleDescriptionImpl()
            .setName("correlation-test")
            .setCreatedAt(OffsetDateTime.parse("2020-08-28T04:32:50.7697024Z"))
            .setAction(new EmptyRuleActionImpl())
            .setFilter(new CorrelationFilterImpl()
                .setLabel("matching-label"));
        final RuleDescriptionEntryImpl expected = new RuleDescriptionEntryImpl()
            .setId("sb://sb-java-conniey-3.servicebus.windows.net/topic-10/Subscriptions/subscription/Rules/correl?api-version=2021-05&enrich=false")
            .setPublished(OffsetDateTime.parse("2020-08-28T04:32:50Z"))
            .setUpdated(OffsetDateTime.parse("2020-08-28T04:34:50Z"))
            .setContent(new RuleDescriptionEntryContentImpl()
                .setRuleDescription(expectedRule)
                .setType("application/xml"));

        // Act
        final RuleDescriptionEntryImpl actual = SERIALIZER.deserialize(contents, RuleDescriptionEntryImpl.class);

        // Assert
        assertRuleEntryEquals(expected, actual);
    }

    /**
     * Verify we can deserialize XML from a GET rule that includes an action.
     */
    @Test
    void deserializeRulesFeed() throws IOException {
        // Arrange
        final String contents = getContents("RuleDescriptionFeed.xml");

        final RuleDescriptionImpl defaultRule = new RuleDescriptionImpl()
            .setName("$Default")
            .setCreatedAt(OffsetDateTime.parse("2020-08-12T18:48:00.1005312Z"))
            .setAction(new EmptyRuleActionImpl())
            .setFilter(new TrueFilterImpl().setCompatibilityLevel("20").setSqlExpression("1=1"));
        final RuleDescriptionEntryImpl defaultRuleEntry = new RuleDescriptionEntryImpl()
            .setId("https://sb-java-conniey-3.servicebus.windows.net/topic-10/Subscriptions/subscription/rules/$Default?api-version=2021-05")
            .setPublished(OffsetDateTime.parse("2020-08-12T18:48:00Z"))
            .setUpdated(OffsetDateTime.parse("2020-08-12T18:48:00Z"))
            .setContent(new RuleDescriptionEntryContentImpl()
                .setRuleDescription(defaultRule)
                .setType("application/xml"));

        final RuleDescriptionImpl correlation = new RuleDescriptionImpl()
            .setName("correl")
            .setCreatedAt(OffsetDateTime.parse("2020-08-28T04:32:50.7697024Z"))
            .setAction(new EmptyRuleActionImpl())
            .setFilter(new CorrelationFilterImpl()
                .setLabel("matching-label"));
        final RuleDescriptionEntryImpl correlationEntry = new RuleDescriptionEntryImpl()
            .setId("https://sb-java-conniey-3.servicebus.windows.net/topic-10/Subscriptions/subscription/rules/correl?api-version=2021-05")
            .setPublished(OffsetDateTime.parse("2020-08-28T04:32:50Z"))
            .setUpdated(OffsetDateTime.parse("2020-08-28T04:32:50Z"))
            .setContent(new RuleDescriptionEntryContentImpl()
                .setRuleDescription(correlation)
                .setType("application/xml"));

        final RuleDescriptionImpl sqlRule = new RuleDescriptionImpl()
            .setName("foo")
            .setCreatedAt(OffsetDateTime.parse("2020-08-28T04:51:24.9967451Z"))
            .setAction(new SqlRuleActionImpl()
                .setCompatibilityLevel("20")
                .setSqlExpression("set FilterTag = 'true'"))
            .setFilter(new SqlFilterImpl()
                .setCompatibilityLevel("20")
                .setSqlExpression("type = \"TestType\""));
        final RuleDescriptionEntryImpl sqlRuleEntry = new RuleDescriptionEntryImpl()
            .setId("https://sb-java-conniey-3.servicebus.windows.net/topic-10/Subscriptions/subscription/rules/foo?api-version=2021-05")
            .setPublished(OffsetDateTime.parse("2020-08-28T04:32:20Z"))
            .setUpdated(OffsetDateTime.parse("2020-08-28T04:32:20Z"))
            .setContent(new RuleDescriptionEntryContentImpl()
                .setRuleDescription(sqlRule)
                .setType("application/xml"));

        final List<RuleDescriptionEntryImpl> expectedEntries = Arrays.asList(defaultRuleEntry, correlationEntry, sqlRuleEntry);
        final RuleDescriptionFeedImpl expected = new RuleDescriptionFeedImpl()
            .setEntry(expectedEntries)
            .setId("https://sb-java-conniey-3.servicebus.windows.net/topic-10/Subscriptions/subscription/rules?api-version=2021-05&enrich=false&$skip=0&$top=100")
            .setUpdated(OffsetDateTime.parse("2020-08-28T14:59:16Z"));

        // Act
        final RuleDescriptionFeedImpl actual = SERIALIZER.deserialize(contents, RuleDescriptionFeedImpl.class);

        // Assert
        assertNotNull(actual);
        assertEquals(expected.getId(), actual.getId());

        final List<RuleDescriptionEntryImpl> actualEntries = actual.getEntry();
        assertNotNull(actualEntries);
        assertEquals(expectedEntries.size(), actualEntries.size());

        for (int i = 0; i < expected.getEntry().size(); i++) {
            final RuleDescriptionEntryImpl expectedRule = expectedEntries.get(i);
            final RuleDescriptionEntryImpl actualRule = actualEntries.get(i);

            assertRuleEntryEquals(expectedRule, actualRule);
        }
    }

    @Test
    void deserializeRuleEntry() throws IOException {
        // Arrange
        final String contents = getContents("CreateRuleEntry.xml");

        final RuleDescriptionImpl description = new RuleDescriptionImpl()
            .setName("connies-bar")
            .setAction(new SqlRuleActionImpl().setSqlExpression("SET Label = 'my-label'"))
            .setFilter(new TrueFilterImpl().setSqlExpression("1=1"));
        final RuleDescriptionEntryContentImpl content = new RuleDescriptionEntryContentImpl()
            .setRuleDescription(description)
            .setType("application/xml");
        final RuleDescriptionEntryImpl expected = new RuleDescriptionEntryImpl().setContent(content);

        // Act
        final RuleDescriptionEntryImpl actual = SERIALIZER.deserialize(contents, RuleDescriptionEntryImpl.class);

        // Assert
        assertRuleEntryEquals(expected, actual);
    }

    @Test
    void deserializeRuleEntryResponse() throws IOException {
        // Arrange
        final String contents = getContents("CreateRuleEntryResponse.xml");

        final RuleDescriptionImpl description = new RuleDescriptionImpl()
            .setName("connies-bar")
            .setAction(new SqlRuleActionImpl().setSqlExpression("SET Label = 'my-label'").setCompatibilityLevel("20"))
            .setFilter(new TrueFilterImpl().setSqlExpression("1=1").setCompatibilityLevel("20"))
            .setCreatedAt(OffsetDateTime.parse("2020-10-05T23:34:21.5963322Z"));
        final RuleDescriptionEntryContentImpl content = new RuleDescriptionEntryContentImpl()
            .setRuleDescription(description)
            .setType("application/xml");
        final RuleDescriptionEntryImpl expected = new RuleDescriptionEntryImpl()
            .setId("https://sb-java.servicebus.windows.net/topic-1/Subscriptions/subscription/Rules/connies-bar?api-version=2021-05")
            .setPublished(OffsetDateTime.parse("2020-10-05T23:31:21Z"))
            .setUpdated(OffsetDateTime.parse("2020-10-05T23:30:21Z"))
            .setLink(new ResponseLinkImpl()
                .setRel("self")
                .setHref("https://sb-java.servicebus.windows.net/topic-1/Subscriptions/subscription/Rules/connies-bar?api-version=2021-05"))
            .setContent(content);

        // Act
        final RuleDescriptionEntryImpl actual = SERIALIZER.deserialize(contents, RuleDescriptionEntryImpl.class);

        // Assert
        assertRuleEntryEquals(expected, actual);
    }

    /**
     * Given a file name, gets the corresponding resource and its contents as a string.
     *
     * @param fileName Name of file to fetch.
     *
     * @return Contents of the file.
     */
    private String getContents(String fileName) {
        final URL resourceUrl = getClass().getClassLoader().getResource(".");
        assertNotNull(resourceUrl);

        final File resourceFolder = new File(resourceUrl.getFile(), "xml");
        assertTrue(resourceFolder.exists());

        final Path path = Paths.get(resourceFolder.getPath(), fileName);
        try {
            return new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
        } catch (IOException e) {
            fail(String.format("Unable to read file: '  %s'. Error: %s", path.getFileName(), e));
            return null;
        }
    }

    private static void assertQueueEquals(CreateQueueOptions expected, EntityStatus expectedStatus,
        QueueDescriptionImpl actual) {

        assertEquals(expected.getAutoDeleteOnIdle(), actual.getAutoDeleteOnIdle());
        assertEquals(expected.getLockDuration(), actual.getLockDuration());

        assertEquals(expected.getMaxSizeInMegabytes(), actual.getMaxSizeInMegabytes());
        assertEquals(expected.isDuplicateDetectionRequired(), actual.isRequiresDuplicateDetection());
        assertEquals(expected.isSessionRequired(), actual.isRequiresSession());
        assertEquals(expected.getDefaultMessageTimeToLive(), actual.getDefaultMessageTimeToLive());
        assertEquals(expected.isDeadLetteringOnMessageExpiration(), actual.isDeadLetteringOnMessageExpiration());
        assertEquals(expected.getDuplicateDetectionHistoryTimeWindow(), actual.getDuplicateDetectionHistoryTimeWindow());
        assertEquals(expected.getMaxDeliveryCount(), actual.getMaxDeliveryCount());
        assertEquals(expected.isBatchedOperationsEnabled(), actual.isEnableBatchedOperations());

        assertEquals(expected.getAutoDeleteOnIdle(), actual.getAutoDeleteOnIdle());
        assertEquals(expected.isPartitioningEnabled(), actual.isEnablePartitioning());

        assertEquals(expectedStatus, actual.getStatus());
    }

    private static void assertSubscriptionEquals(SubscriptionDescriptionImpl expected, EntityStatus expectedStatus,
        SubscriptionDescriptionImpl actual) {

        assertEquals(expected.getAutoDeleteOnIdle(), actual.getAutoDeleteOnIdle());
        assertEquals(expected.getLockDuration(), actual.getLockDuration());
        assertEquals(expected.isDeadLetteringOnFilterEvaluationExceptions(),
            actual.isDeadLetteringOnFilterEvaluationExceptions());
        assertEquals(expected.isRequiresSession(), actual.isRequiresSession());
        assertEquals(expected.getDefaultMessageTimeToLive(), actual.getDefaultMessageTimeToLive());
        assertEquals(expected.isDeadLetteringOnMessageExpiration(), actual.isDeadLetteringOnMessageExpiration());
        assertEquals(expected.getMaxDeliveryCount(), actual.getMaxDeliveryCount());
        assertEquals(expected.isEnableBatchedOperations(), actual.isEnableBatchedOperations());
        assertEquals(expected.getAutoDeleteOnIdle(), actual.getAutoDeleteOnIdle());

        assertEquals(expectedStatus, actual.getStatus());
    }

    private static void assertRuleEntryEquals(RuleDescriptionEntryImpl expected, RuleDescriptionEntryImpl actual) {
        if (expected == null) {
            assertNull(actual);
            return;
        }

        assertNotNull(actual);
        assertEquals(expected.getId(), actual.getId());

        if (expected.getContent() == null) {
            assertNull(actual.getContent());
            return;
        }

        assertNotNull(actual.getContent());
        assertEquals(expected.getContent().getType(), actual.getContent().getType());

        final RuleDescriptionImpl expectedRule = expected.getContent().getRuleDescription();
        final RuleDescriptionImpl actualRule = actual.getContent().getRuleDescription();
        assertNotNull(actualRule);
        assertRuleEquals(expectedRule, actualRule);
    }

    private static void assertRuleEquals(RuleDescriptionImpl expected, RuleDescriptionImpl actual) {
        if (expected == null) {
            assertNull(actual);
            return;
        }

        assertNotNull(actual);
        assertEquals(expected.getName(), actual.getName());

        // Rule action assertions.
        if (expected.getAction() instanceof EmptyRuleActionImpl) {
            assertTrue(actual.getAction() instanceof EmptyRuleActionImpl);
        } else if (expected.getAction() instanceof SqlRuleActionImpl) {
            assertTrue(actual.getAction() instanceof SqlRuleActionImpl);

            final SqlRuleActionImpl expectedAction = (SqlRuleActionImpl) expected.getAction();
            final SqlRuleActionImpl actualAction = (SqlRuleActionImpl) actual.getAction();

            assertEquals(expectedAction.getCompatibilityLevel(), actualAction.getCompatibilityLevel());
            assertEquals(expectedAction.getSqlExpression(), actualAction.getSqlExpression());
            assertEquals(expectedAction.isRequiresPreprocessing(), actualAction.isRequiresPreprocessing());

            assertParameters(expectedAction.getParameters(), actualAction.getParameters());
        }

        // Rule filter assertions.
        if (expected.getFilter() instanceof TrueFilterImpl) {
            assertTrue(actual.getFilter() instanceof TrueFilterImpl);
        } else if (expected.getFilter() instanceof FalseFilterImpl) {
            assertTrue(actual.getFilter() instanceof FalseFilterImpl);
        }

        if (expected.getFilter() instanceof SqlFilterImpl) {
            assertTrue(actual.getFilter() instanceof SqlFilterImpl);

            final SqlFilterImpl expectedFilter = (SqlFilterImpl) expected.getFilter();
            final SqlFilterImpl actualFilter = (SqlFilterImpl) actual.getFilter();

            assertEquals(expectedFilter.getCompatibilityLevel(), actualFilter.getCompatibilityLevel());
            assertEquals(expectedFilter.getSqlExpression(), actualFilter.getSqlExpression());

            assertParameters(expectedFilter.getParameters(), actualFilter.getParameters());
        } else if (expected.getFilter() instanceof CorrelationFilterImpl) {
            assertTrue(actual.getFilter() instanceof CorrelationFilterImpl);

            final CorrelationFilterImpl expectedFilter = (CorrelationFilterImpl) expected.getFilter();
            final CorrelationFilterImpl actualFilter = (CorrelationFilterImpl) actual.getFilter();

            assertEquals(expectedFilter.getCorrelationId(), actualFilter.getCorrelationId());
            assertEquals(expectedFilter.getMessageId(), actualFilter.getMessageId());
            assertEquals(expectedFilter.getTo(), actualFilter.getTo());
            assertEquals(expectedFilter.getReplyTo(), actualFilter.getReplyTo());
            assertEquals(expectedFilter.getReplyToSessionId(), actualFilter.getReplyToSessionId());
            assertEquals(expectedFilter.getSessionId(), actualFilter.getSessionId());
            assertEquals(expectedFilter.getContentType(), actualFilter.getContentType());

            assertParameters(expectedFilter.getProperties(), actualFilter.getProperties());
        }
    }

    private static void assertParameters(List<KeyValueImpl> expected, List<KeyValueImpl> actual) {
        if (expected == null) {
            assertNull(actual);
            return;
        }

        assertNotNull(actual);
        assertEquals(expected.size(), actual.size());

        final Map<String, KeyValueImpl> actualMap = actual.stream()
            .collect(Collectors.toMap(KeyValueImpl::getKey, Function.identity()));

        for (KeyValueImpl item : expected) {
            final KeyValueImpl removed = actualMap.remove(item.getKey());

            assertNotNull(removed);
            assertEquals(item.getValue(), removed.getValue());
        }

        assertTrue(actualMap.isEmpty());
    }

    private static void assertTitle(String expectedTitle, TitleImpl responseTitle) {
        assertEquals(responseTitle.getContent(), expectedTitle);
    }

    private static void assertResponseTitle(TitleImpl expectedResponseTitle, TitleImpl actualResponseTitle) {
        assertEquals(expectedResponseTitle.getContent(), actualResponseTitle.getContent());
    }

    private static TitleImpl getResponseTitle(String entityName) {
        return new TitleImpl().setType("text").setContent(entityName);
    }

    private static class TestAuthorizationRule implements AuthorizationRule {
        private final List<AccessRights> accessRights;
        private final String claimType;
        private final String claimValue;
        private final String keyName;
        private final OffsetDateTime createdAt;
        private final OffsetDateTime modifiedAt;
        private final String primaryKey;
        private final String secondaryKey;

        TestAuthorizationRule(AuthorizationRuleImpl rule) {
            this.accessRights = rule.getRights();
            this.claimType = rule.getClaimType();
            this.claimValue = rule.getClaimValue();
            this.createdAt = rule.getCreatedTime();
            this.keyName = rule.getKeyName();
            this.modifiedAt = rule.getModifiedTime();
            this.primaryKey = rule.getPrimaryKey();
            this.secondaryKey = rule.getSecondaryKey();
        }

        @Override
        public List<AccessRights> getAccessRights() {
            return accessRights;
        }

        @Override
        public String getClaimType() {
            return claimType;
        }

        @Override
        public String getClaimValue() {
            return claimValue;
        }

        @Override
        public OffsetDateTime getCreatedAt() {
            return createdAt;
        }

        @Override
        public String getKeyName() {
            return keyName;
        }

        @Override
        public OffsetDateTime getModifiedAt() {
            return modifiedAt;
        }

        @Override
        public String getPrimaryKey() {
            return primaryKey;
        }

        @Override
        public String getSecondaryKey() {
            return secondaryKey;
        }
    }
}
