package com.atlassian.stash.internal.plugin;

import org.hibernate.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;

import static com.google.common.base.Preconditions.checkNotNull;

@Repository("pluginSettingDao")
@SuppressWarnings("ConstantConditions")
public class HibernatePluginSettingDao implements PluginSettingDao {

    private final SessionFactory sessionFactory;

    @Autowired
    public HibernatePluginSettingDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    private Session session() {
        return sessionFactory.getCurrentSession();
    }

    @Override
    public void attach(@Nonnull PluginSetting setting) {
        try {
            session().buildLockRequest(LockOptions.NONE).lock(setting);
        } catch (NonUniqueObjectException e) {
            // a different instance with a different ID was already attached to the session
            session().evict(access(setting.getNamespace(), setting.getKey()).getReference());
            session().buildLockRequest(LockOptions.NONE).lock(setting);
        }
    }

    @Override
    public void evict(@Nonnull String namespace, @Nonnull String key) {
        PluginSetting setting = getByKey(namespace, key);
        if (setting != null) {
            session().evict(setting);
        }
    }

    @Nonnull
    @Override
    public PluginSetting put(@Nonnull String namespace, @Nonnull String key, @Nonnull String val) {
        checkNotNull(namespace, "namespace");
        checkNotNull(key, "key");

        PluginSetting setting = getByKey(namespace, key);

        if (setting == null) {
            setting = new PluginSetting.Builder()
                    .key(key)
                    .namespace(namespace)
                    .value(val)
                    .build();

            session().saveOrUpdate(setting);
        } else if (!Objects.equals(setting.getValue(), val)) {
            // As an optimisation, we only modify the setting if the value has changed
            setting = new PluginSetting.Builder(setting)
                    .value(val)
                    .build();

            session().merge(setting);
        }
        return setting;
    }

    @Override
    public String get(@Nonnull String namespace, @Nonnull String key) {
        if (namespace == null || key == null) {
            return null;
        }

        PluginSetting result = getByKey(namespace, key);
        return result == null ? null : result.getValue();
    }

    @Override
    public void remove(@Nonnull String namespace, @Nonnull String key) {
        if (namespace == null || key == null) {
            return;
        }

        PluginSetting setting = getByKey(namespace, key);
        if (setting != null) {
            session().delete(setting);
        }
    }

    private NaturalIdLoadAccess access(String namespace, String key) {
        return session().byNaturalId(PluginSetting.class)
                .using("namespace", namespace)
                .using("key", key);
    }

    private PluginSetting getByKey(String namespace, String key) {
        return (PluginSetting) access(namespace, key).load();
    }
}
