package com.atlassian.stash.internal.user;

import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.setting.InternalSharedLob;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import javax.annotation.Nonnull;
import javax.persistence.*;

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

/**
 * Used to determine manage metadata associated to a Stash user
 *
 * @since 3.2
 */
@Cacheable
@Entity
@Table(name = InternalUserSettings.TABLE)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class InternalUserSettings {

    public static final String TABLE = "sta_user_settings";

    // This entity id will match the InternalApplicationUser.id for the associated user
    @Id
    @Column(name = "id", nullable = false)
    private final int id;

    //On cascades: Due to how AbstractHibernateDao works, the JPA PERSIST event is never raised for our entities. To
    //cascade creation, Hibernate's SAVE_UPDATE is the right event. However, because our entities are immutable, the
    //only Hibernate operation capable of updating them is merge, so the JPA MERGE event is required to handle that.
    //As a result, this association has to be mapped with both a "cascade" attribute on the @OneToOne and Hibernate's
    //@Cascade to get the right set of events.
    @JoinColumn(name = "lob_id", foreignKey = @ForeignKey(name = "fk_sta_user_settings_lob"), nullable = false)
    @OneToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY, optional = false)
    @org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
    private final InternalSharedLob settings;

    @SuppressWarnings("unused") //Required by Hibernate
    protected InternalUserSettings() {
        id = (int) ApplicationConstants.UNSAVED_ENTITY;
        settings = null;
    }

    private InternalUserSettings(Builder builder) {
        id = builder.id;
        settings = builder.settings.build();
    }

    public int getId() {
        return id;
    }

    @Nonnull
    public InternalSharedLob getSettings() {
        return settings;
    }

    public static class Builder {

        private int id;
        private InternalSharedLob.Builder settings;

        public Builder() {
            settings = new InternalSharedLob.Builder();
        }

        public Builder(@Nonnull InternalUserSettings userSettings) {
            checkNotNull(userSettings, "userSettings");

            id = userSettings.getId();
            settings = new InternalSharedLob.Builder(userSettings.getSettings());
        }

        @Nonnull
        public InternalUserSettings build() {
            checkState(id != ApplicationConstants.UNSAVED_ENTITY, "No user ID is set. The user must be provided");

            return new InternalUserSettings(this);
        }

        @Nonnull
        public Builder user(@Nonnull InternalApplicationUser value) {
            id = checkNotNull(value, "user").getId();

            return this;
        }

        @Nonnull
        public Builder settings(@Nonnull InternalSharedLob value) {
            settings = new InternalSharedLob.Builder(value);

            return this;
        }

        @Nonnull
        public Builder settings(@Nonnull String value) {
            settings.data(value);

            return this;
        }
    }
}
