package com.atlassian.stash.internal.repository.ref.restriction.dao;

import com.atlassian.bitbucket.repository.ref.restriction.RefRestrictionType;
import com.atlassian.stash.internal.repository.ref.restriction.provider.BranchRestrictionType;
import com.atlassian.stash.internal.repository.ref.restriction.provider.PatternRestrictionType;
import com.google.common.collect.ImmutableMap;
import net.java.ao.DBParam;
import net.java.ao.EntityManager;
import net.java.ao.RawEntity;
import net.java.ao.test.jdbc.DatabaseUpdater;

import java.beans.PropertyChangeListener;
import java.util.Map;

import static org.junit.Assert.assertEquals;

public class RefRestrictionDatabaseUpdater implements DatabaseUpdater {

    public static final String BR_MASTER = "refs/heads/master";
    public static final String BR_1_0 = "refs/heads/1.0";
    public static final String BR_1_2_X_FEATURE_BRANCH = "refs/heads/1.2.x/features/branch-permission";

    public static final String PATTERN1 = "refs/heads/release/**";

    public static final int REPO_1 = 1;
    public static final int REPO_2 = 2;

    public static final String TYPE_BRANCH = BranchRestrictionType.getInstance().getId();
    public static final String TYPE_PATTERN = PatternRestrictionType.getInstance().getId();

    public static final String GROUP_1 = "stash-users";
    public static final String GROUP_2 = "stash-admins";
    public static final String GROUP_3 = "stash-project-creators";

    public static final int USER_1 = 1;
    public static final int USER_2 = 2;

    public static final Map<Integer, TestRefRestriction> TEST_RESTRICTIONS = ImmutableMap.<Integer, TestRefRestriction>builder()
            .put(1, new TestRefRestriction(1, REPO_1, TYPE_BRANCH, BR_MASTER, RefRestrictionType.READ_ONLY))
            .put(2, new TestRefRestriction(2, REPO_1, TYPE_BRANCH, BR_1_0, RefRestrictionType.PULL_REQUEST_ONLY))
            .put(3, new TestRefRestriction(3, REPO_1, TYPE_BRANCH, BR_1_2_X_FEATURE_BRANCH, RefRestrictionType.READ_ONLY))
            .put(4, new TestRefRestriction(4, REPO_2, TYPE_BRANCH, BR_MASTER, RefRestrictionType.FAST_FORWARD_ONLY))
            .put(5, new TestRefRestriction(5, REPO_2, TYPE_PATTERN, PATTERN1, RefRestrictionType.NO_DELETES))
            .put(6, new TestRefRestriction(6, REPO_2, TYPE_PATTERN, PATTERN1, RefRestrictionType.FAST_FORWARD_ONLY))
            .build();

    public static final Map<Integer, TestAccessGrant> TEST_ACCESS_GRANTS = ImmutableMap.<Integer, TestAccessGrant>builder()
            .put(1, new TestAccessGrant(USER_1))
            .put(2, new TestAccessGrant(USER_2))
            .put(3, new TestAccessGrant(GROUP_1))
            .put(4, new TestAccessGrant(GROUP_2))
            .put(5, new TestAccessGrant(GROUP_3))
            .put(6, new TestAccessGrant(USER_1))
            .put(7, new TestAccessGrant(GROUP_1))
            .build();

    @Override
    public void update(EntityManager entityManager) throws Exception {
        entityManager.migrate(AoRefRestriction.class, AoAccessGrant.class);
        for (Map.Entry<Integer, TestRefRestriction> entry : TEST_RESTRICTIONS.entrySet()) {
            assertEquals(entry.getKey(), entityManager.create(
                    AoRefRestriction.class,
                    new DBParam(AoRefRestriction.REPOSITORY_ID_COLUMN, entry.getValue().getRepositoryId()),
                    new DBParam(AoRefRestriction.TYPE_COLUMN, entry.getValue().getType()),
                    new DBParam(AoRefRestriction.REF_MATCHER_TYPE_COLUMN, entry.getValue().getMatcherType()),
                    new DBParam(AoRefRestriction.REF_MATCHER_ID_COLUMN, entry.getValue().getMatcherId())
            ).getId());
        }

        TEST_ACCESS_GRANTS.forEach((integer, testAccessGrant) -> {
            if (integer >= 2 && integer <= 6) {
                testAccessGrant.setRestriction(TEST_RESTRICTIONS.get(2));
            } else {
                testAccessGrant.setRestriction(TEST_RESTRICTIONS.get(1));
            }
        });

        for (Map.Entry<Integer, TestAccessGrant> entry : TEST_ACCESS_GRANTS.entrySet()) {
            assertEquals(entry.getKey(), entityManager.create(
                    AoAccessGrant.class,
                    new DBParam(AoAccessGrant.RESTRICTION_COLUMN, entry.getValue().getRestriction().getId()),
                    new DBParam(AoAccessGrant.GROUP_NAME_COLUMN, entry.getValue().getGroup()),
                    new DBParam(AoAccessGrant.USER_ID_COLUMN, entry.getValue().getUserId())
            ).getId());
        }
    }

    abstract static class TestRawEntity<T> implements RawEntity<T> {
        @Override
        public void init() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void save() {
            throw new UnsupportedOperationException();
        }

        @Override
        public EntityManager getEntityManager() {
            throw new UnsupportedOperationException();
        }

        @Override
        public <X extends RawEntity<T>> Class<X> getEntityType() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void addPropertyChangeListener(PropertyChangeListener listener) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void removePropertyChangeListener(PropertyChangeListener listener) {
            throw new UnsupportedOperationException();
        }
    }

    static class TestRefRestriction extends TestRawEntity<Integer> implements AoRefRestriction {

        private final int id;
        private final int repositoryId;
        private final String matcherType;
        private final String matcherId;
        private final RefRestrictionType type;

        TestRefRestriction(int id, int repositoryId, String matcherType, String matcherId, RefRestrictionType type) {
            this.id = id;
            this.repositoryId = repositoryId;
            this.matcherType = matcherType;
            this.matcherId = matcherId;
            this.type = type;
        }

        @Override
        public AoAccessGrant[] getAccessGrants() {
            return new TestAccessGrant[]{TEST_ACCESS_GRANTS.get(this.id)};
        }

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

        @Override
        public Integer getRepositoryId() {
            return repositoryId;
        }

        @Override
        public String getMatcherId() {
            return matcherId;
        }

        @Override
        public String getMatcherType() {
            return matcherType;
        }

        @Override
        public String getType() {
            return type.getId();
        }
    }

    static class TestAccessGrant extends TestRawEntity<Integer> implements AoAccessGrant {

        private AoRefRestriction restriction;
        private final String groupName;
        private final Integer userId;

        TestAccessGrant(String groupName) {
            this.groupName = groupName;
            this.userId = null;
        }

        TestAccessGrant(int userId) {
            this.groupName = null;
            this.userId = userId;
        }

        @Override
        public Integer getId() {
            throw new UnsupportedOperationException();
        }

        @Override
        public AoRefRestriction getRestriction() {
            return restriction;
        }

        @Override
        public void setRestriction(AoRefRestriction restriction) {
            this.restriction = restriction;
        }

        @Override
        public String getGroup() {
            return groupName;
        }

        @Override
        public Integer getUserId() {
            return userId;
        }
    }
}
