package com.atlassian.stash.internal.auth;

import com.atlassian.stash.internal.ApplicationConstants;
import com.atlassian.stash.internal.Initializable;
import com.atlassian.stash.internal.entity.InternalAbstractEntity;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.validation.annotation.OptionalString;
import com.atlassian.bitbucket.validation.annotation.RequiredString;
import com.google.common.primitives.Ints;
import org.hibernate.Hibernate;

import javax.annotation.Nonnull;
import javax.persistence.*;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.concurrent.TimeUnit;

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

/**
 * @since 3.8
 */
@Entity
@Table(name = InternalRememberMeToken.TABLE)
@TableGenerator(allocationSize = 20, pkColumnValue = InternalRememberMeToken.TABLE,
        name = InternalAbstractEntity.ID_GEN, table = ApplicationConstants.ID_SEQUENCE_TABLE)
public class InternalRememberMeToken extends InternalAbstractEntity implements Initializable {

    static final String TABLE = "sta_remember_me_token";

    @Column(name = "series", nullable = false, updatable = false)
    @RequiredString(size = 64)
    private final String series;
    @Column(name = "token", nullable = false, updatable = false)
    @RequiredString(size = 64)
    private final String token;
    @Column(name = "claimed", nullable = false)
    private final boolean claimed;
    @Column(name = "expiry_timestamp", nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private final Date expiryDate;
    @Column(name = "claimed_address")
    @OptionalString(size = 255)
    private final String remoteAddress;
    @JoinColumn(name = "user_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private final InternalApplicationUser user;

    protected InternalRememberMeToken() { // for Hibernate
        claimed = false;
        expiryDate = null;
        remoteAddress = null;
        series = null;
        token = null;
        user = null;
    }

    private InternalRememberMeToken(Builder builder) {
        claimed = builder.claimed;
        expiryDate = builder.expiryDate;
        remoteAddress = builder.remoteAddress;
        series = builder.series;
        token = builder.token;
        user = builder.user;
    }

    @Nonnull
    public Date getExpiryDate() {
        return new Date(expiryDate.getTime());
    }

    public String getRemoteAddress() {
        return remoteAddress;
    }

    @Nonnull
    public String getSeries() {
        return series;
    }

    @Nonnull
    public String getToken() {
        return token;
    }

    public ApplicationUser getUser() {
        return user;
    }

    @Override
    public void initialize() {
        Hibernate.initialize(getUser());
    }

    public boolean isClaimed() {
        return claimed;
    }

    public boolean isExpired() {
        return System.currentTimeMillis() > expiryDate.getTime();
    }

    public static class Builder extends AbstractEntityBuilder<Builder, InternalRememberMeToken> {
        private boolean claimed;
        private Date expiryDate;
        private String remoteAddress;
        private String series;
        private String token;
        private InternalApplicationUser user;

        public Builder() {
        }

        public Builder(@Nonnull InternalRememberMeToken token) {
            super(token);

            this.claimed = token.claimed;
            this.expiryDate = checkNotNull(token, "token").expiryDate;
            this.remoteAddress = token.remoteAddress;
            this.series = token.series;
            this.token = token.token;
            this.user = token.user;
        }

        @Nonnull
        public InternalRememberMeToken build() {
            checkState(series != null, "Series must be set");
            checkState(token != null, "Token must be set");

            return new InternalRememberMeToken(this);
        }

        @Nonnull
        public Builder claimed() {
            claimed = true;
            return self();
        }

        @Nonnull
        public Builder expiresAfter(long time, @Nonnull TimeUnit timeUnit) {
            checkNotNull(timeUnit, "timeUnit");

            GregorianCalendar calendar = new GregorianCalendar();
            calendar.add(Calendar.SECOND, Ints.saturatedCast(timeUnit.toSeconds(time)));
            expiryDate = calendar.getTime();
            return self();
        }

        @Nonnull
        public Builder remoteAddress(@Nonnull String value) {
            remoteAddress = checkNotNull(value, "remoteAddress");
            return self();
        }

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

        @Nonnull
        public Builder series(@Nonnull String value) {
            series = checkNotNull(value, "series");
            return self();
        }

        @Nonnull
        public Builder token(@Nonnull String value) {
            this.token = checkNotNull(value, "token");
            return self();
        }

        @Nonnull
        public Builder user(@Nonnull InternalApplicationUser value) {
            user = checkNotNull(value, "user");
            return self();
        }
    }
}
