package de.koudingspawn.vault;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
import de.koudingspawn.vault.crd.Vault;
import de.koudingspawn.vault.crd.VaultDockerCfgConfiguration;
import de.koudingspawn.vault.crd.VaultSpec;
import de.koudingspawn.vault.crd.VaultType;
import de.koudingspawn.vault.kubernetes.EventHandler;
import de.koudingspawn.vault.kubernetes.scheduler.impl.DockerCfgRefresh;
import de.koudingspawn.vault.vault.communication.SecretNotAccessibleException;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.Base64;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
import static de.koudingspawn.vault.Constants.COMPARE_ANNOTATION;
import static de.koudingspawn.vault.Constants.LAST_UPDATE_ANNOTATION;
import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest(
        properties = {
                "kubernetes.vault.url=http://localhost:8202/v1/",
                "kubernetes.initial-delay=5000000"
        }

)
public class DockerCfgTest {

    @ClassRule
    public static WireMockClassRule wireMockClassRule =
            new WireMockClassRule(wireMockConfig().port(8202));

    @Rule
    public WireMockClassRule instanceRule = wireMockClassRule;

    @Autowired
    public EventHandler handler;

    @Autowired
    public DockerCfgRefresh dockerCfgRefresh;

    @Autowired
    public KubernetesClient client;

    @org.springframework.boot.test.context.TestConfiguration
    static class KindConfig {

        @Bean
        @Primary
        public KubernetesClient client() {
            KubernetesClient kubernetesClient = new DefaultKubernetesClient();
            TestHelper.createCrd(kubernetesClient);

            return kubernetesClient;
        }

    }

    @Before
    public void before() {
        WireMock.resetAllScenarios();
        client.secrets().inAnyNamespace().delete();

        TestHelper.generateLookupSelfStub();
    }

    @Test
    public void shouldGenerateDockerCfgFromVaultResource() throws IOException {
        Vault vault = new Vault();
        vault.setMetadata(
            new ObjectMetaBuilder().withName("dockercfg").withNamespace("default").build()
        );
        VaultSpec spec = new VaultSpec();
        spec.setType(VaultType.DOCKERCFG);
        spec.setPath("secret/docker");
        vault.setSpec(spec);

        stubFor(get(urlEqualTo("/v1/secret/docker"))
                .inScenario("Simple Vault request")
                .whenScenarioStateIs(STARTED)
            .willReturn(aResponse()
                .withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\n" +
                        "  \"request_id\": \"6cc090a8-3821-8244-73e4-5ab62b605587\",\n" +
                        "  \"lease_id\": \"\",\n" +
                        "  \"renewable\": false,\n" +
                        "  \"lease_duration\": 2764800,\n" +
                        "  \"data\": {\n" +
                        "    \"username\": \"username\",\n" +
                        "    \"password\": \"password\",\n" +
                        "    \"url\": \"hub.docker.com\",\n" +
                        "    \"email\": \"[email protected]\"\n" +
                        "  },\n" +
                        "  \"wrap_info\": null,\n" +
                        "  \"warnings\": null,\n" +
                        "  \"auth\": null\n" +
                        "}")));

        handler.addHandler(vault);

        Secret secret = client.secrets().inNamespace("default").withName("dockercfg").get();

        assertEquals("dockercfg", secret.getMetadata().getName());
        assertEquals("default", secret.getMetadata().getNamespace());
        assertEquals("kubernetes.io/dockercfg", secret.getType());
        assertNotNull(secret.getMetadata().getAnnotations().get("vault.koudingspawn.de" + LAST_UPDATE_ANNOTATION));
        assertEquals("+gE+L0DNsGWDlNz5T3jLp1/U08KbD4OF+ez2lXQlTPM=", secret.getMetadata().getAnnotations().get("vault.koudingspawn.de" + COMPARE_ANNOTATION));

        String dockerCfgBase64 = secret.getData().get(".dockercfg");
        String dockerCfg = new String(Base64.getDecoder().decode(dockerCfgBase64));
        ObjectMapper mapper = new ObjectMapper();
        JsonNode dockerCfgNode = mapper.readTree(dockerCfg);
        assertTrue(dockerCfgNode.has("hub.docker.com"));

        JsonNode credentials = dockerCfgNode.get("hub.docker.com");
        assertEquals("username", credentials.get("username").asText());
        assertEquals("password", credentials.get("password").asText());
        assertEquals("[email protected]", credentials.get("email").asText());
        assertEquals("username:password", new String(Base64.getDecoder().decode(credentials.get("auth").asText())));
    }

    @Test
    public void shouldCheckIfDockerCfgHasChangedAndReturnTrue() throws SecretNotAccessibleException {
        Vault vault = new Vault();
        vault.setMetadata(
                new ObjectMetaBuilder().withName("dockercfg").withNamespace("default").build()
        );
        VaultSpec spec = new VaultSpec();
        spec.setType(VaultType.DOCKERCFG);
        spec.setPath("secret/docker");
        vault.setSpec(spec);

        stubFor(get(urlEqualTo("/v1/secret/docker"))
                .inScenario("Docker secret change")
                .whenScenarioStateIs(STARTED)
                .willSetStateTo("Docker first request done")
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"request_id\":\"6cc090a8-3821-8244-73e4-5ab62b605587\",\"lease_id\":\"\",\"renewable\":false,\"lease_duration\":2764800,\"data\":{\"username\":\"username\", \"password\": \"password\", \"url\": \"hub.docker.com\", \"email\": \"[email protected]\"},\"wrap_info\":null,\"warnings\":null,\"auth\":null}")));

        stubFor(get(urlEqualTo("/v1/secret/docker"))
                .inScenario("Docker secret change")
                .whenScenarioStateIs("Docker first request done")
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"request_id\":\"6cc090a8-3821-8244-73e4-5ab62b605587\",\"lease_id\":\"\",\"renewable\":false,\"lease_duration\":2764800,\"data\":{\"username\":\"usernamehaschanged\", \"password\": \"password\", \"url\": \"hub.docker.com\", \"email\": \"[email protected]\"},\"wrap_info\":null,\"warnings\":null,\"auth\":null}")));

        handler.addHandler(vault);
        assertTrue(dockerCfgRefresh.refreshIsNeeded(vault));
    }

    @Test
    public void shouldCheckIfDockerCfgHasChangedAndReturnFalse() throws SecretNotAccessibleException {
        Vault vault = new Vault();
        vault.setMetadata(
                new ObjectMetaBuilder().withName("dockercfg").withNamespace("default").build()
        );
        VaultSpec spec = new VaultSpec();
        spec.setType(VaultType.DOCKERCFG);
        spec.setPath("secret/docker");
        vault.setSpec(spec);

        stubFor(get(urlEqualTo("/v1/secret/docker"))
                .inScenario("Simple Vault request")
                .whenScenarioStateIs(STARTED)
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\"request_id\":\"6cc090a8-3821-8244-73e4-5ab62b605587\",\"lease_id\":\"\",\"renewable\":false,\"lease_duration\":2764800,\"data\":{\"username\":\"username\", \"password\": \"password\", \"url\": \"hub.docker.com\", \"email\": \"[email protected]\"},\"wrap_info\":null,\"warnings\":null,\"auth\":null}")));

        handler.addHandler(vault);
        assertFalse(dockerCfgRefresh.refreshIsNeeded(vault));
    }

    @Test
    public void shouldGenerateDockerCfgV2() throws JsonProcessingException {
        Vault vault = new Vault();
        vault.setMetadata(
                new ObjectMetaBuilder().withName("dockercfg").withNamespace("default").build()
        );
        VaultSpec spec = new VaultSpec();
        spec.setType(VaultType.DOCKERCFG);
        spec.setPath("secret/docker");

        VaultDockerCfgConfiguration dockerConfig = new VaultDockerCfgConfiguration();
        dockerConfig.setType(VaultType.KEYVALUEV2);
        spec.setDockerCfgConfiguration(dockerConfig);
        vault.setSpec(spec);

        stubFor(get(urlPathMatching("/v1/secret/data/docker"))
                .willReturn(aResponse()
                        .withStatus(200)
                        .withHeader("Content-Type", "application/json")
                        .withBody("{\n" +
                                "  \"request_id\": \"1cfee2a6-318a-ea12-f5b5-6fd52d74d2c6\",\n" +
                                "  \"lease_id\": \"\",\n" +
                                "  \"renewable\": false,\n" +
                                "  \"lease_duration\": 0,\n" +
                                "  \"data\": {\n" +
                                "    \"data\": {\n" +
                                "      \"username\": \"username\",\n" +
                                "      \"password\": \"password\",\n" +
                                "      \"url\": \"hub.docker.com\",\n" +
                                "      \"email\": \"[email protected]\"\n" +
                                "    },\n" +
                                "    \"metadata\": {\n" +
                                "      \"created_time\": \"2018-12-10T18:59:53.337997525Z\",\n" +
                                "      \"deletion_time\": \"\",\n" +
                                "      \"destroyed\": false,\n" +
                                "      \"version\": 1\n" +
                                "    }\n" +
                                "  },\n" +
                                "  \"wrap_info\": null,\n" +
                                "  \"warnings\": null,\n" +
                                "  \"auth\": null\n" +
                                "}")));

        handler.addHandler(vault);

        Secret secret = client.secrets().inNamespace("default").withName("dockercfg").get();
        assertEquals("dockercfg", secret.getMetadata().getName());
        assertEquals("default", secret.getMetadata().getNamespace());
        assertEquals("kubernetes.io/dockercfg", secret.getType());
        assertNotNull(secret.getMetadata().getAnnotations().get("vault.koudingspawn.de" + LAST_UPDATE_ANNOTATION));
        assertEquals("+gE+L0DNsGWDlNz5T3jLp1/U08KbD4OF+ez2lXQlTPM=", secret.getMetadata().getAnnotations().get("vault.koudingspawn.de" + COMPARE_ANNOTATION));

        String dockerCfgBase64 = secret.getData().get(".dockercfg");
        String dockerCfg = new String(Base64.getDecoder().decode(dockerCfgBase64));
        ObjectMapper mapper = new ObjectMapper();
        JsonNode dockerCfgNode = mapper.readTree(dockerCfg);
        assertTrue(dockerCfgNode.has("hub.docker.com"));

        JsonNode credentials = dockerCfgNode.get("hub.docker.com");
        assertEquals("username", credentials.get("username").asText());
        assertEquals("password", credentials.get("password").asText());
        assertEquals("[email protected]", credentials.get("email").asText());
        assertEquals("username:password", new String(Base64.getDecoder().decode(credentials.get("auth").asText())));
    }

    @After
    @Before
    public void cleanup() {
        Secret secret = client.secrets().inNamespace("default").withName("dockercfg").get();
        if (secret != null) {
            client.secrets().inNamespace("default").withName("dockercfg").cascading(true).delete();
        }
    }

    @AfterClass
    public static void cleanupK8S() {
        KubernetesClient kubernetesClient = new DefaultKubernetesClient();
        TestHelper.deleteCRD(kubernetesClient);
    }

}