package com.atlassian.bitbucket.internal.mirroring.mirror.jwt;

import com.atlassian.bitbucket.hamcrest.MatcherBuilder;
import com.atlassian.bitbucket.hamcrest.OptionalMatcher;
import com.atlassian.bitbucket.permission.Permission;
import com.google.common.collect.Maps;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.junit.Test;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import static com.atlassian.bitbucket.hamcrest.OptionalMatcher.some;
import static org.junit.Assert.assertThat;

public class JwtContextUserTest {

    @Test
    public void testValueOfAcceptsUserWithUserNameAndKeyAndSlug() {
        String username = "user";
        String key = "key";
        String slug = "slug";

        UserMapBuilder userBuilder = user().username(username).userKey(key).slug(slug);
        Optional<JwtContextUser> actual = JwtContextUser.valueOf(userBuilder.build());

        assertThat(actual, some(matchesUser(key, username).slug(slug)));
    }

    @Test
    public void testValueOfParsesOutPermission() {
        String username = "user";
        String key = "key";
        String slug = "slug";
        Permission permission = Permission.ADMIN;

        UserMapBuilder userBuilder = user().username(username)
                .userKey(key)
                .slug(slug)
                .permission(permission);

        Optional<JwtContextUser> actual = JwtContextUser.valueOf(userBuilder.build());
        assertThat(actual, some(matchesUser(key, username).slug(slug).highestPermission(permission)));
    }

    @Test
    public void testValueOfParsesOutPermissionOfWrongCase() {
        String username = "user";
        String key = "key";
        String slug = "slug";
        Permission permission = Permission.ADMIN;

        UserMapBuilder userBuilder = user().username(username)
                .userKey(key)
                .slug(slug)
                .permission(permission.name().toLowerCase(Locale.ROOT));

        Optional<JwtContextUser> actual = JwtContextUser.valueOf(userBuilder.build());
        assertThat(actual, some(matchesUser(key, username).slug(slug).highestPermission(permission)));
    }

    @Test
    public void testValueOfIgnoresInvalidPermission() {
        String username = "user";
        String key = "key";
        String slug = "slug";

        UserMapBuilder userBuilder = user().username(username)
                .userKey(key)
                .slug(slug)
                .permission("badPermission");

        Optional<JwtContextUser> actual = JwtContextUser.valueOf(userBuilder.build());
        assertThat(actual, some(matchesUser(key, username).slug(slug)));
    }

    @Test
    public void testValueOfParsesOutDisplayName() {
        String username = "user";
        String key = "key";
        String displayName = "displayName";

        UserMapBuilder userBuilder = user().username(username)
                .userKey(key)
                .slug(username)
                .displayName(displayName);

        Optional<JwtContextUser> actual = JwtContextUser.valueOf(userBuilder.build());
        assertThat(actual, some(matchesUser(key, username).displayName(displayName)));
    }

    @Test
    public void testValueOfRejectsUserWithNoName() {
        String key = "key";
        String slug = "slug";

        UserMapBuilder userBuilder = user().userKey(key).slug(slug);

        Optional<JwtContextUser> actual = JwtContextUser.valueOf(userBuilder.build());
        assertThat(actual, OptionalMatcher.empty());
    }

    @Test
    public void testValueOfRejectsUserWithNoKey() {
        String username = "username";
        String slug = "slug";

        UserMapBuilder userBuilder = user().username(username).slug(slug);

        Optional<JwtContextUser> actual = JwtContextUser.valueOf(userBuilder.build());
        assertThat(actual, OptionalMatcher.empty());
    }

    @Test
    public void testValueOfRejectsUserWithNoSlug() {
        String username = "username";
        String key = "key";

        UserMapBuilder userBuilder = user().username(username).userKey(key);

        Optional<JwtContextUser> actual = JwtContextUser.valueOf(userBuilder.build());
        assertThat(actual, OptionalMatcher.empty());
    }

    private static JwtContextUserMatcher matchesUser(String key, String username) {
        return new JwtContextUserMatcher()
                .displayName(username)
                .username(username)
                .slug(username)
                .userKey(key);
    }

    private static UserMapBuilder user() {
        return new UserMapBuilder();
    }

    private static class JwtContextUserMatcher extends TypeSafeDiagnosingMatcher<JwtContextUser> {
        private String username;
        private String userKey;
        private Permission highestPermission;
        private String slug;
        private String displayName;

        private JwtContextUserMatcher username(String value) {
            username = value;
            return this;
        }

        private JwtContextUserMatcher userKey(String value) {
            userKey = value;
            return this;
        }

        private JwtContextUserMatcher highestPermission(Permission value) {
            highestPermission = value;
            return this;
        }

        private JwtContextUserMatcher slug(String value) {
            slug = value;
            return this;
        }

        private JwtContextUserMatcher displayName(String value) {
            displayName = value;
            return this;
        }

        @Override
        protected boolean matchesSafely(JwtContextUser item, Description mismatchDescription) {
            return new MatcherBuilder()
                    .equal("username", username, item.getName())
                    .equal("userKey", userKey, item.getKey())
                    .equal("highestPermission", highestPermission, item.getHighestPermission())
                    .equal("slug", slug, item.getSlug())
                    .equal("displayName", displayName, item.getDisplayName())
                    .collect(mismatchDescription);
        }

        @Override
        public void describeTo(Description description) {
            StringWriter buffer = new StringWriter();
            PrintWriter writer = new PrintWriter(buffer);
            writer.printf("{username: %s", username);
            writer.printf(", userKey: %s", userKey);
            writer.printf(", highestPermission: %s", highestPermission);
            writer.printf(", slug: %s", slug);
            writer.printf(", displayName: %s}", displayName);
            writer.close();
            description.appendText(buffer.toString());
        }
    }

    private static class UserMapBuilder {
        private Map<String, Object> map = Maps.newHashMap();

        private UserMapBuilder username(String value) {
            map.put("username", value);
            return this;
        }

        private UserMapBuilder userKey(String value) {
            map.put("userKey", value);
            return this;
        }

        private UserMapBuilder slug(String value) {
            map.put("slug", value);
            return this;
        }

        private UserMapBuilder permission(Permission value) {
            map.put("highestPermission", value.name());
            return this;
        }

        private UserMapBuilder permission(String value) {
            map.put("highestPermission", value);
            return this;
        }

        private UserMapBuilder displayName(String value) {
            map.put("displayName", value);
            return this;
        }

        public Map<String, Object> build() {
            return new HashMap<>(map);
        }
    }
}