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

import com.atlassian.bitbucket.internal.mirroring.mirror.InternalUpstreamServer;
import com.atlassian.bitbucket.internal.mirroring.mirror.MirroringConfig;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamAccount;
import com.atlassian.bitbucket.mirroring.mirror.UpstreamServerType;
import org.apache.commons.lang.StringUtils;

import javax.annotation.Nonnull;
import java.util.StringJoiner;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.commons.lang.StringUtils.removeStart;

class DefaultCloudUpstreamNavBuilder implements CloudUpstreamNavBuilder {

    private static final String API_VERSION = "2.0";

    private final String apiBaseUrl;
    private final String baseUrl;
    private final MirroringConfig mirroringConfig;
    private final MirroringNavBuilder navBuilder;
    private final InternalUpstreamServer upstream;

    DefaultCloudUpstreamNavBuilder(MirroringConfig mirroringConfig, MirroringNavBuilder navBuilder, InternalUpstreamServer upstream) {
        checkArgument(upstream.getType() == UpstreamServerType.BITBUCKET_CLOUD, "upstream must be Bitbucket Cloud");

        this.mirroringConfig = mirroringConfig;
        this.navBuilder = navBuilder;
        this.upstream = upstream;

        baseUrl = cleanUrl(upstream.getBaseUrl());
        apiBaseUrl = cleanUrl(upstream.getApiBaseUrl());
    }

    @Nonnull
    @Override
    public MirroringUrl admin() {
        // URL format: /account/user/{Team name}/addon/configure/{addon key}/mirror-config
        // note: team name is used instead of team UUID here so that a nice URL is provided to the end user
        return new PathUrlBuilder(baseUrl, "account", "user", getAccountName(),
                "addon", "configure", mirroringConfig.getAddonKey(), "mirror-config");
    }

    @Nonnull
    @Override
    public MirroringUrl authorizeAddon() {
        String redirectUrl = navBuilder.placeholder().getAbsolute();
        String descriptorUrl = navBuilder.rest()
                .upstreamServer(upstream.getId())
                .addon()
                .descriptor()
                .getAbsolute();

        return new PathUrlBuilder(baseUrl, "site", "addons", "authorize")
                .parameter("descriptor_uri", descriptorUrl)
                .parameter("redirect_uri", redirectUrl);
    }

    @Nonnull
    @Override
    public MirroringUrl addonStatus(@Nonnull String addonKey) {
        // URL format: /internal/account/{Team UUID}/addons/{addon key}/status

        return new PathUrlBuilder(apiBaseUrl,
                "internal", "account", getAccountId(), "addons", addonKey, "status");
    }

    @Nonnull
    @Override
    public OAuth2 oauth2() {
        return new SimpleOAuth();
    }

    @Nonnull
    @Override
    public MirroringUrl relativeToBase(@Nonnull String path) {
        return new PathUrlBuilder(baseUrl, removeStart(checkNotNull(path, "path"), "/").split("/"));
    }

    @Nonnull
    @Override
    public MirroringUrl repositories(int limit) {
        // URL format: /2.0/repositories/{Team UUID}
        return new PathUrlBuilder(apiBaseUrl, API_VERSION, "repositories", getAccountId())
                .parameter("pagelen", limit)
                .parameter("fields", repositoriesFields());
    }

    @Nonnull
    @Override
    public MirroringUrl repositoryById(@Nonnull String id) {
        // URL format: /2.0/repositories/{}/{Repo UUID}
        return new PathUrlBuilder(apiBaseUrl, API_VERSION, "repositories", "{}", id)
                .parameter("fields", repositoryFields());
    }

    @Nonnull
    @Override
    public MirroringUrl repositoryBySlug(@Nonnull String slug) {
        // URL format: /2.0/repositories/{Team UUID}/slug
        return new PathUrlBuilder(apiBaseUrl, API_VERSION, "repositories", getAccountId(), slug)
                .parameter("fields", repositoryFields());
    }

    @Nonnull
    @Override
    public MirroringUrl repositoryPermissionBySlug(@Nonnull String slug) {
        // URL format: /internal/repositories/{Team name}/slug/permission
        // TODO: verify if the account ID can be used instead of account name
        return new PathUrlBuilder(apiBaseUrl, "internal", "repositories", getAccountName(), slug,
                "permission");
    }

    @Nonnull
    @Override
    public MirroringUrl account() {
        return new PathUrlBuilder(apiBaseUrl, API_VERSION, "teams", getAccountId());
    }

    private class SimpleOAuth implements OAuth2 {

        @Nonnull
        @Override
        public MirroringUrl accessToken() {
            return new PathUrlBuilder(baseUrl, "site", "oauth2", "access_token");
        }
    }

    private static String cleanUrl(String baseUrl) {
        return StringUtils.stripEnd(baseUrl, "/\\");
    }

    private String getAccountName() {
        return this.upstream.getAccount().map(UpstreamAccount::getName)
                .orElseThrow(() -> new IllegalStateException("This mirror has not yet been installed so no upstream account name is available"));
    }

    private String getAccountId() {
        return this.upstream.getAccount().map(UpstreamAccount::getId)
                .orElseThrow(() -> new IllegalStateException("This mirror has not yet been installed so no upstream account ID is available"));
    }

    private String repositoriesFields() {
        return new StringJoiner(",")
                .add("values.parent.owner")
                .add("values.parent.updated_on")
                .add("values.parent.parent.owner")
                .add("values.parent.parent.updated_on")
                .add("values.parent.parent.parent.owner")
                .add("values.parent.parent.parent.updated_on")
                .add("values.parent.parent.parent.parent.owner")
                .add("values.parent.parent.parent.parent.updated_on")
                .add("values.parent.parent.parent.parent.parent.owner")
                .add("values.parent.parent.parent.parent.parent.updated_on")
                .toString();
    }

    private String repositoryFields() {
        return new StringJoiner(",")
                .add("parent.owner")
                .add("parent.updated_on")
                .add("parent.parent.owner")
                .add("parent.parent.updated_on")
                .add("parent.parent.parent.owner")
                .add("parent.parent.parent.updated_on")
                .add("parent.parent.parent.parent.owner")
                .add("parent.parent.parent.parent.updated_on")
                .add("parent.parent.parent.parent.parent.owner")
                .add("parent.parent.parent.parent.parent.updated_on")
                .toString();
    }
}
