package com.atlassian.stash.internal.task;

import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.HibernateUtils;
import com.atlassian.stash.internal.Initializable;
import com.atlassian.stash.internal.comment.InternalComment;
import com.atlassian.stash.internal.entity.InternalAbstractEntity;
import com.atlassian.stash.internal.property.AbstractPropertySupport;
import com.atlassian.stash.internal.pull.InternalPullRequest;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.bitbucket.task.Task;
import com.atlassian.bitbucket.task.TaskAnchor;
import com.atlassian.bitbucket.task.TaskOperations;
import com.atlassian.bitbucket.task.TaskState;
import com.google.common.base.Function;
import org.hibernate.annotations.CacheConcurrencyStrategy;

import javax.annotation.Nonnull;
import javax.persistence.*;
import java.util.Date;

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

/**
 * @since 3.3
 */
@Cacheable
@Entity
@Table(name = InternalTask.TABLE, indexes = {
        @Index(name = "idx_sta_task_anchor", columnList = "anchor_type, anchor_id"),
        @Index(name = "idx_sta_task_context", columnList = "context_type, context_id")
})
@TableGenerator(allocationSize = 20, pkColumnValue = InternalTask.TABLE,
        name = InternalTask.ID_GEN, table = ApplicationConstants.ID_SEQUENCE_TABLE)
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class InternalTask extends AbstractPropertySupport implements Task, Initializable {

    public static final String ID_GEN = InternalAbstractEntity.ID_GEN; // for backward compatibility
    public static final String TABLE = "sta_task";

    public static Function<Task, Long> TO_ID = new Function<Task, Long>() {
        @Override
        public Long apply(Task input) {
            return input.getId();
        }
    };

    @SuppressWarnings("JpaAttributeTypeInspection")
    @JoinColumn(name = TaskAnchorType.COLUMN_ANCHOR_ID, nullable = false, updatable = false)
    @org.hibernate.annotations.Any(fetch = FetchType.LAZY, optional = false,
            metaColumn = @Column(name = TaskAnchorType.COLUMN_ANCHOR_TYPE, nullable = false))
    @org.hibernate.annotations.AnyMetaDef(idType = "long", metaType = "integer", metaValues = {
            @org.hibernate.annotations.MetaValue(targetEntity = InternalComment.class, value = TaskAnchorType.COMMENT_DISCRIMINATOR)
    })
    private TaskAnchor anchor; //Yes, not final. See initialize()

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

    @SuppressWarnings("JpaAttributeTypeInspection")
    @JoinColumn(name = TaskContextType.COLUMN_CONTEXT_ID, nullable = false, updatable = false)
    @org.hibernate.annotations.Any(fetch = FetchType.LAZY, optional = false,
            metaColumn = @Column(name = TaskContextType.COLUMN_CONTEXT_TYPE, nullable = false))
    @org.hibernate.annotations.AnyMetaDef(idType = "long", metaType = "integer", metaValues = {
            @org.hibernate.annotations.MetaValue(targetEntity = InternalPullRequest.class, value = TaskContextType.PULL_REQUEST_DISCRIMINATOR)
    })
    private InternalTaskContext context; //Yes, not final. See initialize()

    @Column(name = "created_timestamp", nullable = false, updatable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private final Date createdDate;

    @Column(name = "id", nullable = false, unique = true)
    @Id
    @GeneratedValue(generator = ID_GEN, strategy = GenerationType.TABLE)
    private final long id;

    /** Permitted operations by the current user */
    @Transient
    private TaskOperations permittedOperations;

    @Column(name = "task_state", nullable = false)
    @org.hibernate.annotations.Type(type = "com.atlassian.hibernate.extras.type.GenericEnumUserType", parameters = {
            @org.hibernate.annotations.Parameter(name = "enumClass", value = "com.atlassian.bitbucket.task.TaskState")
    })
    private final TaskState state;

    @Column(name = "task_text", nullable = false)
    @Lob
    @org.hibernate.annotations.Type(type = "com.atlassian.hibernate.extras.type.ClobType")
    private final String text;

    @SuppressWarnings("unused") //Required for Hibernate
    protected InternalTask() {
        anchor = null;
        author = null;
        context = null;
        createdDate = null;
        id = ApplicationConstants.UNSAVED_ENTITY;
        state = null;
        text = null;
    }

    private InternalTask(Builder builder) {
        super(builder);

        this.anchor = builder.anchor;
        this.author = builder.author;
        this.context = builder.context;
        this.createdDate = builder.createdDate;
        this.id = builder.id;
        this.state = builder.state;
        this.text = builder.text;
    }

    @Nonnull
    @Override
    public TaskAnchor getAnchor() {
        return anchor;
    }

    @Nonnull
    @Override
    public InternalApplicationUser getAuthor() {
        return author;
    }

    @Nonnull
    public InternalTaskContext getContext() {
        return context;
    }

    @Nonnull
    @Override
    public Date getCreatedDate() {
        return createdDate;
    }

    @Override
    public long getId() {
        return id;
    }

    @Override
    public TaskOperations getPermittedOperations() {
        return permittedOperations;
    }

    @Nonnull
    @Override
    public TaskState getState() {
        return state;
    }

    @Nonnull
    @Override
    public String getText() {
        return text;
    }

    @Override
    public boolean hasProperties() {
        return super.hasProperties();
    }

    @Override
    public void initialize() {
        anchor = HibernateUtils.initialize(getAnchor());
        author = HibernateUtils.initialize(getAuthor());
        context = HibernateUtils.initialize(getContext());
    }

    public void setPermittedOperations(@Nonnull TaskOperations operations) {
        if (this.permittedOperations != null) {
            throw new IllegalStateException("permittedOperations has already been set");
        }
        this.permittedOperations = operations;
    }

    public static class Builder extends PropertyBuilderSupport<Builder> {

        private TaskAnchor anchor;
        private InternalApplicationUser author;
        private InternalTaskContext context;
        private Date createdDate;
        private long id;
        private TaskState state = TaskState.OPEN;
        private String text;

        public Builder() {
            id = ApplicationConstants.UNSAVED_ENTITY;
        }

        public Builder(@Nonnull InternalTask task) {
            super(checkNotNull(task, "task"));

            anchor = task.getAnchor();
            author = task.getAuthor();
            context = task.getContext();
            createdDate = task.getCreatedDate();
            id = task.getId();
            state = task.getState();
            text = task.getText();
        }

        @Nonnull
        public Builder anchor(@Nonnull TaskAnchor value) {
            anchor = checkNotNull(value, "anchor");

            return self();
        }

        @Nonnull
        public Builder author(@Nonnull InternalApplicationUser value) {
            author = checkNotNull(value, "author");

            return self();
        }

        @Nonnull
        public InternalTask build() {
            return new InternalTask(this);
        }

        @Nonnull
        public Builder context(@Nonnull InternalTaskContext value) {
            context = checkNotNull(value, "context");

            return self();
        }

        @Nonnull
        public Builder createdDate(@Nonnull Date value) {
            // STASHDEV-7682 - SQLServer and MySQL don't handle milliseconds, so strip them
            Date dateWithMs = checkNotNull(value, "createdDate");
            Date dateWithoutMs = new Date((dateWithMs.getTime() / 1000) * 1000);

            createdDate = dateWithoutMs;

            return self();
        }

        @Nonnull
        public Builder id(long value) {
            id = value;

            return self();
        }

        @Nonnull
        public Builder state(@Nonnull TaskState value) {
            state = checkNotNull(value, "state");

            return self();
        }

        @Nonnull
        public Builder text(@Nonnull String value) {
            text = checkNotBlank(value, "text");

            return self();
        }

        @Nonnull
        @Override
        protected Builder self() {
            return this;
        }
    }
}
