package dev.fitko.fitconnect.integrationtests;

import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.domain.model.destination.ContactInformation;
import dev.fitko.fitconnect.api.domain.model.destination.CreateDestination;
import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.destination.DestinationService;
import dev.fitko.fitconnect.api.domain.model.destination.Destinations;
import dev.fitko.fitconnect.api.domain.model.destination.StatusEnum;
import dev.fitko.fitconnect.api.domain.model.jwk.ApiJwk;
import dev.fitko.fitconnect.api.domain.model.jwk.ApiJwks;
import dev.fitko.fitconnect.api.domain.model.jwk.KeyOpsEnum;
import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
import dev.fitko.fitconnect.client.DestinationClient;
import dev.fitko.fitconnect.client.bootstrap.ClientFactory;
import dev.fitko.fitconnect.core.crypto.utils.TestKeyBuilder;
import dev.fitko.fitconnect.integrationtests.condition.EnableIfEnvironmentVariablesAreSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.net.URI;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

@EnableIfEnvironmentVariablesAreSet
public class DestinationClientIT extends IntegrationTestBase {

    private DestinationClient destinationClient;

    @BeforeEach
    void setup() {
        final ApplicationConfig config = getConfigWithCredentialsFromEnvironment();
        destinationClient = ClientFactory.createDestinationClient(config);
    }

    @Test
    public void testCreateDestination() {

        // Given
        final CreateDestination createDestination = createNewTestDestination();

        // When
        final Destination destination = destinationClient.createDestination(createDestination);

        // Then
        assertNotNull(destination);
        assertThat(destination.getName(), is(createDestination.getName()));
        assertThat(destination.getStatus(), is(StatusEnum.CREATED));
        assertThat(destination.getCallback(), is(createDestination.getCallback()));
        assertThat(destination.getServices(), is(createDestination.getServices()));
        assertThat(destination.getContactInformation(), is(createDestination.getContactInformation()));
        assertThat(destination.getMetadataVersions(), is(createDestination.getMetadataVersions()));
        assertThat(destination.getEncryptionKid(), is(createDestination.getEncryptionKid()));
        assertThat(destination.getReplyChannels(), is(createDestination.getReplyChannels()));
    }

    @Test
    public void testCreateIncompleteDestination() {

        // Given
        final CreateDestination createDestination = CreateDestination.builder()
                .name("Incomplete Draft Destination")
                .status(StatusEnum.DRAFT)
                .build();

        // When
        final Destination incompleteDestination = destinationClient.createDestination(createDestination);

        // Then
        assertNotNull(incompleteDestination);

        assertThat(incompleteDestination.getServices(), is(empty()));
        assertThat(incompleteDestination.getMetadataVersions(), is(empty()));
        assertThat(incompleteDestination.getEncryptionKid(), isEmptyOrNullString());

        assertThat(incompleteDestination.getName(), is(createDestination.getName()));
        assertThat(incompleteDestination.getStatus(), is(createDestination.getStatus()));
        assertThat(incompleteDestination.getCallback(), is(createDestination.getCallback()));
        assertThat(incompleteDestination.getContactInformation(), is(createDestination.getContactInformation()));
        assertThat(incompleteDestination.getReplyChannels(), is(createDestination.getReplyChannels()));
    }

    @Test
    public void testUpdateDestination() {

        // Given
        final CreateDestination createDestination = CreateDestination.builder()
                .name("Incomplete Draft Destination")
                .status(StatusEnum.DRAFT)
                .build();

        final SubmissionSchema schema = new SubmissionSchema();
        schema.setSchemaUri(URI.create("https://test.schema.net"));
        schema.setMimeType(MimeType.APPLICATION_JSON);

        final DestinationService service = new DestinationService();
        service.setRegions(Set.of("DE100"));
        service.setSubmissionSchemas(Set.of(schema));
        service.setIdentifier("urn:de:fim:leika:leistung:99400048079000");

        final ApiJwk publicEncryptionKey = TestKeyBuilder.generateEncryptionKeyPair().getPublicApiJwk();

        final Destination draftDestination = destinationClient.createDestination(createDestination);
        destinationClient.addKeyToDestination(draftDestination.getDestinationId(), publicEncryptionKey);

        // When
        draftDestination.setServices(Set.of(service));
        draftDestination.setMetadataVersions(Set.of("1.5.0", "1.4.0"));
        draftDestination.setEncryptionKid(publicEncryptionKey.getKid());

        final Destination updatedDestination = destinationClient.updateDestination(draftDestination);

        // Then
        assertNotNull(updatedDestination);
        assertThat(draftDestination.getServices(), is(updatedDestination.getServices()));
        assertThat(draftDestination.getMetadataVersions(), is(updatedDestination.getMetadataVersions()));
        assertThat(draftDestination.getEncryptionKid(), is(updatedDestination.getEncryptionKid()));
    }

    @Test
    public void testListDestinations() {

        // Given
        final Destinations availableDestinations = destinationClient.listDestinations(0, 10);

        // When
        final List<Destination> destinations = availableDestinations.getDestinations().stream()
                .map(Destination::getDestinationId)
                .map(destinationClient::getDestination)
                .collect(Collectors.toList());

        // Then
        assertNotNull(destinations);
        assertThat(destinations, hasSize(availableDestinations.getCount()));
    }

    @Test
    public void testListKeysForDestination() {

        // Given
        var destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID"));

        // When
        final ApiJwks publicKeys = destinationClient.getKeysForDestination(destinationId, 0, 10);

        // Then
        assertNotNull(publicKeys);
        assertTrue(publicKeys.getKeys().stream().flatMap(apiJwk -> apiJwk.getKeyOps().stream()).anyMatch(k -> k.equals(KeyOpsEnum.WRAPKEY)));
        assertTrue(publicKeys.getKeys().stream().flatMap(apiJwk -> apiJwk.getKeyOps().stream()).anyMatch(k -> k.equals(KeyOpsEnum.VERIFY)));
    }

    @Test
    public void testGetSingleKeyForDestination() {

        // Given
        var destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID"));
        final ApiJwk expectedKey = destinationClient.getKeysForDestination(destinationId, 0, 1).getKeys().iterator().next();

        // When
        final ApiJwk publicKey = destinationClient.getKeyForDestination(destinationId, expectedKey.getKid());

        // Then
        assertNotNull(publicKey);
        assertThat(publicKey, is(expectedKey));
    }

    @Test
    public void testAddKeyForDestination() {

        // Given
        var destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID"));
        var publicEncryptionKey = TestKeyBuilder.generateEncryptionKeyPair().getPublicApiJwk();
        var keyCountBeforeUpdate = destinationClient.getKeysForDestination(destinationId, 0, 500).getKeys().size();

        // When
        destinationClient.addKeyToDestination(destinationId, publicEncryptionKey);
        Set<ApiJwk> updatedKeys = destinationClient.getKeysForDestination(destinationId, 0, 500).getKeys();

        // Then
        assertNotNull(updatedKeys);
        assertThat(updatedKeys.size(), is(keyCountBeforeUpdate + 1));
        assertTrue(updatedKeys.stream().anyMatch(k -> k.getKid().equals(publicEncryptionKey.getKid())));
    }


    private static CreateDestination createNewTestDestination() {

        final ApiJwk publicEncryptionKey = TestKeyBuilder.generateEncryptionKeyPair().getPublicApiJwk();
        final ApiJwk publicSigningKey = TestKeyBuilder.generateSignatureKeyPair().getPublicApiJwk();

        final ContactInformation contactInformation = new ContactInformation();
        contactInformation.setAddress("Musterstr. 42");
        contactInformation.setEmail("test@mail.net");
        contactInformation.setPhone("0123456789");
        contactInformation.setLegalName("Test Inc.");

        final SubmissionSchema schema = new SubmissionSchema();
        schema.setSchemaUri(URI.create("https://test.schema.net"));
        schema.setMimeType(MimeType.APPLICATION_JSON);

        final DestinationService service = new DestinationService();
        service.setRegions(Set.of("DE100"));
        service.setSubmissionSchemas(Set.of(schema));
        service.setIdentifier("urn:de:fim:leika:leistung:99400048079000");

        return CreateDestination.builder()
                .name("Test Destination")
                .metadataVersions(Set.of("1.3.0", "1.4.0"))
                .contactInformation(contactInformation)
                .services(Set.of(service))
                .encryptionKid(publicEncryptionKey.getKid())
                .encryptionPublicKey(publicEncryptionKey)
                .signingPublicKey(publicSigningKey)
                .build();
    }
}