package com.atlassian.stash.internal.watcher;

import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.HibernateUtils;
import com.atlassian.stash.internal.Initializable;
import com.atlassian.stash.internal.commit.InternalCommitDiscussion;
import com.atlassian.stash.internal.pull.InternalPullRequest;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.bitbucket.watcher.Watcher;
import org.hibernate.annotations.CacheConcurrencyStrategy;

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

@Cacheable
@Entity
@Table(name = InternalWatcher.TABLE, uniqueConstraints = {
        @UniqueConstraint(name = "uq_sta_watchable", columnNames = {"watchable_id", "watchable_type", "user_id"})
})
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@org.hibernate.annotations.Immutable
public class InternalWatcher implements Initializable, Watcher {

    public static final String ID_GEN = "watcherIdGenerator";
    public static final String TABLE = "sta_watcher";

    @Column(name = "id", nullable = false, unique = true)
    @Id
    @GeneratedValue(generator = ID_GEN, strategy = GenerationType.TABLE)
    @TableGenerator(allocationSize = 20, pkColumnValue = TABLE, name = ID_GEN, table = ApplicationConstants.ID_SEQUENCE_TABLE)
    private final Long id;

    @JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "fk_sta_watcher_user"), updatable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private InternalApplicationUser user; //Yes, not final. See initialize()

    @SuppressWarnings("JpaAttributeTypeInspection")
    @JoinColumn(name = WatchableType.COLUMN_WATCHABLE_ID, nullable = false, updatable = false)
    @org.hibernate.annotations.Any(fetch = FetchType.LAZY, optional = false,
            metaColumn = @Column(name = WatchableType.COLUMN_WATCHABLE_TYPE, nullable = false))
    @org.hibernate.annotations.AnyMetaDef(idType = "long", metaType = "integer", metaValues = {
            @org.hibernate.annotations.MetaValue(targetEntity = InternalCommitDiscussion.class, value = WatchableType.COMMIT_DISCUSSION_DISCRIMINATOR),
            @org.hibernate.annotations.MetaValue(targetEntity = InternalPullRequest.class, value = WatchableType.PULL_REQUEST_DISCRIMINATOR)
    })
    private InternalWatchable watchable; //Yes, not final. See initialize()

    @SuppressWarnings("unused") //Required for Hibernate
    protected InternalWatcher() {
        id = null;
    }

    private InternalWatcher(Long id, InternalApplicationUser user, InternalWatchable watchable) {
        this.id = id;
        this.user = user;
        this.watchable = watchable;
    }

    @Nullable
    public Long getId() {
        return id;
    }

    @Nonnull
    @Override
    public InternalApplicationUser getUser() {
        return user;
    }

    @Nonnull
    @Override
    public InternalWatchable getWatchable() {
        return watchable;
    }

    @Override
    public void initialize() {
        user = HibernateUtils.initialize(getUser());
        watchable = HibernateUtils.initialize(getWatchable());
    }

    public static class Builder {

        private Long id;
        private InternalApplicationUser user;
        private InternalWatchable watchable;

        public Builder() {
        }

        public Builder(InternalWatcher watcher) {
            id = watcher.getId();
            user = watcher.getUser();
            watchable = watcher.getWatchable();
        }

        public InternalWatcher build() {
            return new InternalWatcher(id, user, watchable);
        }

        public Builder id(Long value) {
            id = value;

            return this;
        }

        public Builder user(InternalApplicationUser value) {
            user = value;

            return this;
        }

        public Builder watchable(InternalWatchable value) {
            watchable = value;

            return this;
        }
    }
}
