package org.cloudfoundry.credhub.db.migration;

import java.util.List;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.cloudfoundry.credhub.CredhubTestApp;
import org.cloudfoundry.credhub.entities.EncryptionKeyCanary;
import org.cloudfoundry.credhub.repositories.EncryptionKeyCanaryRepository;
import org.cloudfoundry.credhub.utils.DatabaseProfileResolver;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.MigrationVersion;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

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

@RunWith(SpringRunner.class)
@ActiveProfiles(value = "unit-test", resolver = DatabaseProfileResolver.class)
@SpringBootTest(classes = CredhubTestApp.class)
public class EarlyCredentialMigrationTest {

    @Autowired
    private Flyway flyway;
    @Autowired
    private Environment environment;
    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private EncryptionKeyCanaryRepository encryptionKeyCanaryRepository;

    private long id = 0;
    private List<EncryptionKeyCanary> canaries;

    @Before
    public void beforeEach() {
        canaries = encryptionKeyCanaryRepository.findAll();

    Flyway flywayV4 = Flyway
      .configure()
      .target(MigrationVersion.fromVersion("4"))
      .dataSource(flyway.getConfiguration().getDataSource())
      .locations(flyway.getConfiguration().getLocations())
      .load();

    flywayV4.clean();
    flywayV4.migrate();
  }

  @After
  public void afterEach() {
    flyway.clean();
    flyway.migrate();

        encryptionKeyCanaryRepository.saveAll(canaries);
        encryptionKeyCanaryRepository.flush();
    }

    @Test
    public void successfullyAppliesLatestMigration() {
        jdbcTemplate.update(
                "insert into named_canary (id, name, encrypted_value, nonce) values (?, ?, ?, ?)",
                10, "canary", "encrypted-value".getBytes(UTF_8), "nonce".getBytes(UTF_8)
        );

        // we use raw sql because the entities assume the latest version
        storeValueSecret("test");
        storeValueSecret("/test");
        storeValueSecret("/deploy123/test");
    }

    @SuppressFBWarnings(
            value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE",
            justification = "Ignore that JDBCTemplate might return a null"
    )
    private void storeValueSecret(final String credentialName) {
        final MapSqlParameterSource paramSource = new MapSqlParameterSource();
        final String uuid = UUID.randomUUID().toString().replace("-", "");

        paramSource.addValue("id", id++);
        paramSource.addValue("type", "value");
        paramSource.addValue("encrypted_value", new byte[29]);
        paramSource.addValue("name", credentialName);
        paramSource.addValue("nonce", new byte[16]);
        paramSource.addValue("updated_at", 0);
        paramSource.addValue("uuid", uuid);

        final boolean isPostgres = environment.acceptsProfiles("unit-test-postgres");
        final String sql = "INSERT INTO named_secret("
                + (isPostgres ? "id, " : "")
                + "type, encrypted_value, name, nonce, updated_at, uuid) values ("
                + (isPostgres ? ":id, " : "")
                + ":type, :encrypted_value, :name, :nonce, :updated_at, :uuid)";
        namedParameterJdbcTemplate.update(sql, paramSource);

        final long id = namedParameterJdbcTemplate
                .queryForObject("SELECT id FROM named_secret WHERE name = :name",
                        new MapSqlParameterSource("name", credentialName), Long.class);

        namedParameterJdbcTemplate.update("INSERT INTO value_secret"
                + "(id) values (:id)", new MapSqlParameterSource("id", id));
    }
}