package com.atlassian.stash.internal.project;

import com.atlassian.crowd.embedded.impl.IdentifierUtils;
import com.atlassian.stash.internal.HibernateUtils;
import com.atlassian.stash.internal.user.InternalApplicationUser;
import com.atlassian.stash.internal.user.InternalNormalUser;
import com.atlassian.bitbucket.project.PersonalProject;
import com.atlassian.bitbucket.project.ProjectType;
import com.atlassian.bitbucket.project.ProjectVisitor;
import com.atlassian.bitbucket.user.ApplicationUser;
import org.apache.commons.lang3.StringUtils;

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

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

/**
 * @since 2.4
 */
@DiscriminatorValue("1")
@Entity
@PrimaryKeyJoinColumn(name = "project_id", referencedColumnName = "id")
@Table(name = InternalPersonalProject.TABLE, uniqueConstraints = {
        @UniqueConstraint(name = "uq_sta_personal_project_owner", columnNames = "owner_id")
})
public class InternalPersonalProject extends InternalProject implements PersonalProject {

    public static final String TABLE = "sta_personal_project";

    @JoinColumn(name = "owner_id", nullable = false, unique = true,
            foreignKey = @ForeignKey(name = "fk_sta_personal_project_owner"))
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private InternalApplicationUser owner;

    protected InternalPersonalProject() {
    }

    private InternalPersonalProject(Integer id, String namespace, String key, InternalNormalUser owner) {
        super(id, namespace, key, key, null, ProjectType.PERSONAL);

        this.owner = owner;
    }

    @Override
    public <T> T accept(@Nonnull ProjectVisitor<T> visitor) {
        return visitor.visit(this);
    }

    public Builder copy() {
        return new Builder(this);
    }

    @Nonnull
    @Override
    public String getName() {
        return getOwner().getDisplayName();
    }

    @Nonnull
    @Override
    public InternalNormalUser getOwner() {
        return HibernateUtils.cast(owner, InternalNormalUser.class);
    }

    @Override
    public void initialize() {
        owner = HibernateUtils.initialize(getOwner());
    }

    @Override
    public boolean isPublic() {
        // Personal projects are currently not able to be made explicitly public.
        // They become implicitly public when one of the contained repositories becomes public
        return false;
    }

    /**
     * Creates a project key that can be used to create or retrieve the personal project for the specified
     * {@link ApplicationUser user}.
     *
     * @param user the user to create a personal project key for
     * @return the key to the specified user's personal project
     */
    public static String createPersonalProjectKey(@Nonnull ApplicationUser user) {
        return createPersonalProjectKey(user.getSlug());
    }

    /**
     * Creates a project key that can be used to create or retrieve a personal project for the {@link ApplicationUser}
     * with the specified {@link ApplicationUser#getSlug() slug}.
     *
     * @param slug the slug to create a personal project key from
     * @return the key to the user's personal project
     */
    public static String createPersonalProjectKey(@Nonnull String slug) {
        return "~" + slug;
    }

    /**
     * Returns the username for the owner of the personal project identified by the provided {@code projectKey}.
     *
     * @param projectKey the personal project key
     * @return the username of the owner, or {@code null} if it cannot be determined
     */
    public static String getPersonalProjectOwner(@Nonnull String projectKey) {
        //InternalApplicationUser lowercases its username values using this function. Since project keys are (currently)
        //forced to uppercase, to get a legitimate username we'll want to do the same lowercasing
        return isPersonalProjectKey(projectKey) ? IdentifierUtils.toLowerCase(projectKey.substring(1)) : null;
    }

    /**
     * Retrieves a flag indicating whether the provided {@code projectKey} references a personal project. {@code true}
     * here does <i>not</i> mean that the personal project belongs to the current user; it simply means it belongs to
     * <i>some</i> user.
     *
     * @param projectKey the project key to check
     * @return {@code true} if the key identifies a personal project; otherwise, {@code false} for normal projects
     */
    public static boolean isPersonalProjectKey(@Nonnull String projectKey) {
        return StringUtils.startsWith(projectKey, "~");
    }

    public static class Builder extends AbstractBuilder<Builder> {

        private String key;
        private InternalNormalUser owner;

        public Builder() {
        }

        public Builder(@Nonnull InternalPersonalProject project) {
            super(project);

            key = project.getKey();
            owner = project.getOwner();
        }

        @Nonnull
        public InternalPersonalProject build() {
            checkState(owner != null, "The personal project's owner is required");

            return new InternalPersonalProject(id, namespace, key, owner);
        }

        @Nonnull
        public Builder owner(@Nonnull InternalNormalUser value) {
            owner = checkNotNull(value, "owner");
            key = createPersonalProjectKey(value);

            return self();
        }

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