/*
 * 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.migration.persistence.payload;

import com.codahale.metrics.MetricRegistry;
import com.google.inject.Injector;
import com.hivemq.extension.sdk.api.annotations.NotNull;
import com.hivemq.bootstrap.ioc.GuiceBootstrap;
import com.hivemq.configuration.ConfigurationBootstrap;
import com.hivemq.configuration.HivemqId;
import com.hivemq.configuration.info.SystemInformation;
import com.hivemq.configuration.service.FullConfigurationService;
import com.hivemq.configuration.service.InternalConfigurations;
import com.hivemq.migration.MigrationUnit;
import com.hivemq.migration.Migrations;
import com.hivemq.migration.meta.MetaFileService;
import com.hivemq.migration.meta.MetaInformation;
import com.hivemq.migration.meta.PersistenceType;
import com.hivemq.persistence.PersistenceStartup;
import com.hivemq.persistence.payload.PublishPayloadLocalPersistence;
import com.hivemq.persistence.payload.PublishPayloadRocksDBLocalPersistence;
import com.hivemq.persistence.payload.PublishPayloadXodusLocalPersistence;
import com.hivemq.persistence.retained.RetainedMessageLocalPersistence;
import com.hivemq.util.LocalPersistenceFileUtil;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.io.File;
import java.io.IOException;
import java.util.Map;

import static org.junit.Assert.*;
import static org.mockito.Mockito.when;

/**
 * @author Florian Limpöck
 */
@SuppressWarnings("NullabilityAnnotations")
public class PublishPayloadTypeMigrationTest {

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    @Mock
    private SystemInformation systemInformation;

    private File dataFolder;
    private FullConfigurationService configurationService;

    @Before
    public void before() throws IOException {
        MockitoAnnotations.initMocks(this);
        dataFolder = temporaryFolder.newFolder();
        when(systemInformation.getDataFolder()).thenReturn(dataFolder);
        when(systemInformation.getConfigFolder()).thenReturn(temporaryFolder.newFolder());
        when(systemInformation.getHiveMQVersion()).thenReturn("2019.2");

        InternalConfigurations.PERSISTENCE_BUCKET_COUNT.set(4);
        InternalConfigurations.PAYLOAD_PERSISTENCE_BUCKET_COUNT.set(4);
        final File file = new File(dataFolder, LocalPersistenceFileUtil.PERSISTENCE_SUBFOLDER_NAME);
        file.mkdirs();
        new File(file, RetainedMessageLocalPersistence.PERSISTENCE_NAME).mkdir();
        new File(file, PublishPayloadLocalPersistence.PERSISTENCE_NAME).mkdir();

        configurationService = ConfigurationBootstrap.bootstrapConfig(systemInformation);
    }

    @After
    public void tearDown() throws Exception {
        InternalConfigurations.RETAINED_MESSAGE_PERSISTENCE_TYPE.set(PersistenceType.FILE_NATIVE);
        InternalConfigurations.PAYLOAD_PERSISTENCE_TYPE.set(PersistenceType.FILE_NATIVE);
        InternalConfigurations.PERSISTENCE_BUCKET_COUNT.set(64);
        InternalConfigurations.PAYLOAD_PERSISTENCE_BUCKET_COUNT.set(64);
        FileUtils.forceDelete(dataFolder);
    }

    @Test
    public void test_payload_migration_xodus_to_rocks() throws Exception {


        final Map<MigrationUnit, PersistenceType>
                migrations = Migrations.checkForTypeMigration(systemInformation);

        assertEquals(2, migrations.size());
        assertEquals(PersistenceType.FILE_NATIVE, migrations.get(MigrationUnit.FILE_PERSISTENCE_PUBLISH_PAYLOAD));

        final Injector persistenceInjector =
                GuiceBootstrap.persistenceInjector(systemInformation, new MetricRegistry(), new HivemqId(), configurationService);
        final PersistenceStartup persistenceStartup = persistenceInjector.getInstance(PersistenceStartup.class);
        persistenceStartup.finish();

        final PublishPayloadXodusLocalPersistence xodus = persistenceInjector.getInstance(PublishPayloadXodusLocalPersistence.class);
        for (int i = 0; i < 1000; i++) {
            xodus.put(i, ("message" + i).getBytes());
        }

        Migrations.migrate(persistenceInjector, migrations);

        final PublishPayloadRocksDBLocalPersistence rocks = persistenceInjector.getInstance(PublishPayloadRocksDBLocalPersistence.class);
        for (int i = 0; i < 1000; i++) {
            assertEquals("message" + i, new String(rocks.get(i)));
        }

        Migrations.afterMigration(systemInformation);

        persistenceStartup.run();

        //check meta after migration
        final MetaInformation metaInformation = MetaFileService.readMetaFile(systemInformation);
        assertEquals(PublishPayloadRocksDBLocalPersistence.PERSISTENCE_VERSION, metaInformation.getPublishPayloadPersistenceVersion());
        assertEquals(PersistenceType.FILE_NATIVE, metaInformation.getPublishPayloadPersistenceType());
    }

    @Test
    public void test_payload_migration_rocks_to_xodus() throws Exception {


        MetaFileService.writeMetaFile(systemInformation, getMetaInformation());
        InternalConfigurations.PAYLOAD_PERSISTENCE_TYPE.set(PersistenceType.FILE);

        final Map<MigrationUnit, PersistenceType> migrations = Migrations.checkForTypeMigration(systemInformation);

        assertEquals(1, migrations.size());
        assertEquals(PersistenceType.FILE, migrations.get(MigrationUnit.FILE_PERSISTENCE_PUBLISH_PAYLOAD));

        final Injector persistenceInjector = GuiceBootstrap.persistenceInjector(systemInformation, new MetricRegistry(), new HivemqId(), configurationService);
        final PersistenceStartup persistenceStartup = persistenceInjector.getInstance(PersistenceStartup.class);
        persistenceStartup.finish();

        final PublishPayloadRocksDBLocalPersistence rocks = persistenceInjector.getInstance(PublishPayloadRocksDBLocalPersistence.class);
        for (int i = 0; i < 1000; i++) {
            rocks.put(i, ("message" + i).getBytes());
        }

        Migrations.migrate(persistenceInjector, migrations);

        final PublishPayloadXodusLocalPersistence xodus = persistenceInjector.getInstance(PublishPayloadXodusLocalPersistence.class);
        for (int i = 0; i < 1000; i++) {
            assertEquals("message" + i, new String(xodus.get(i)));
        }

        Migrations.afterMigration(systemInformation);

        persistenceStartup.run();

        //check meta after migration
        final MetaInformation metaInformation = MetaFileService.readMetaFile(systemInformation);
        assertEquals(PublishPayloadXodusLocalPersistence.PERSISTENCE_VERSION, metaInformation.getPublishPayloadPersistenceVersion());
        assertEquals(PersistenceType.FILE, metaInformation.getPublishPayloadPersistenceType());

        InternalConfigurations.PAYLOAD_PERSISTENCE_TYPE.set(PersistenceType.FILE_NATIVE);
    }

    @Test
    public void test_payload_migration_stays_xodus() throws Exception {

        InternalConfigurations.PAYLOAD_PERSISTENCE_TYPE.set(PersistenceType.FILE);

        final Map<MigrationUnit, PersistenceType> migrations = Migrations.checkForTypeMigration(systemInformation);

        assertEquals(1, migrations.size());
        assertFalse(migrations.containsKey(MigrationUnit.FILE_PERSISTENCE_PUBLISH_PAYLOAD));

        final Injector persistenceInjector = GuiceBootstrap.persistenceInjector(systemInformation, new MetricRegistry(), new HivemqId(), configurationService);
        final PersistenceStartup persistenceStartup = persistenceInjector.getInstance(PersistenceStartup.class);
        persistenceStartup.finish();

        final PublishPayloadXodusLocalPersistence persistence_4_2_x = persistenceInjector.getInstance(PublishPayloadXodusLocalPersistence.class);
        for (int i = 0; i < 10; i++) {
            persistence_4_2_x.put(i, ("message" + i).getBytes());
        }

        Migrations.migrate(persistenceInjector, migrations);
        Migrations.afterMigration(systemInformation);

        //closing persistences
        persistenceStartup.run();

        //check meta after migration
        final MetaInformation metaInformation = MetaFileService.readMetaFile(systemInformation);
        assertEquals(PublishPayloadXodusLocalPersistence.PERSISTENCE_VERSION, metaInformation.getPublishPayloadPersistenceVersion());
        assertEquals(PersistenceType.FILE, metaInformation.getPublishPayloadPersistenceType());
        InternalConfigurations.PAYLOAD_PERSISTENCE_TYPE.set(PersistenceType.FILE_NATIVE);

    }

    @NotNull
    private MetaInformation getMetaInformation() {
        final MetaInformation metaInformation = new MetaInformation();

        metaInformation.setHivemqVersion("2019.1");
        metaInformation.setPublishPayloadPersistenceType(PersistenceType.FILE_NATIVE);
        metaInformation.setRetainedMessagesPersistenceType(PersistenceType.FILE_NATIVE);
        metaInformation.setDataFolderPresent(true);
        metaInformation.setMetaFilePresent(true);
        metaInformation.setPersistenceFolderPresent(true);

        return metaInformation;
    }

}