package com.atlassian.bitbucket.internal.importer.bitbucketcloud;

import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.internal.importer.*;
import com.atlassian.bitbucket.server.ApplicationPropertiesService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.util.PageUtils;
import com.atlassian.bitbucket.util.ValidationUtils;
import com.atlassian.httpclient.api.Response;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import javax.annotation.Nonnull;
import javax.validation.Validator;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.atlassian.bitbucket.internal.importer.ImporterConstants.PROPERTY_BITBUCKET_CLOUD_API_URL;
import static com.atlassian.bitbucket.internal.importer.ImporterConstants.PROPERTY_BITBUCKET_CLOUD_API_URL_DEFAULT;
import static com.atlassian.bitbucket.internal.importer.bitbucketcloud.BitbucketCloudUriBuilder.*;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.collections.CollectionUtils.subtract;

public class BitbucketCloudRepositoryService implements ExternalRepositoryService {

    private static final Logger log = LoggerFactory.getLogger(BitbucketCloudRepositoryService.class);

    private final String bbCloudApiUrl;
    private final I18nService i18nService;
    private final ExternalRequestExecutor requestExecutor;
    private final Validator validator;

    @Autowired
    public BitbucketCloudRepositoryService(@Qualifier("i18nService") I18nService i18nService,
                                           @Qualifier("propertiesService") ApplicationPropertiesService propertiesService,
                                           @Qualifier("requestExecutor") ExternalRequestExecutor requestExecutor,
                                           @Qualifier("validator") Validator validator) {
        this.i18nService = i18nService;
        this.requestExecutor = requestExecutor;
        this.validator = validator;

        bbCloudApiUrl = propertiesService.getPluginProperty(PROPERTY_BITBUCKET_CLOUD_API_URL, PROPERTY_BITBUCKET_CLOUD_API_URL_DEFAULT);
    }

    @Nonnull
    @Override
    public Iterable<OAuthScope> getMissingOAuthScopes(@Nonnull ExternalRequest request) {
        List<OAuthScope> oAuthScopes = requestExecutor.executeHeadRequest(
                request.getCredential(),
                buildUserUrl(bbCloudApiUrl, request.getCredential().getUsername()),
                response -> {
                    String scopes = response.getHeader("X-OAuth-Scopes");
                    if (StringUtils.isBlank(scopes)) {
                        return emptyList();
                    }

                    return stream(scopes.split(","))
                            .map(String::trim)
                            .map(BitbucketCloudOAuthScope::fromString)
                            .filter(Optional::isPresent)
                            .map(Optional::get)
                            .map(BitbucketCloudOAuthScope::getScope)
                            .collect(Collectors.toList());
                });
        return subtract(asList(OAuthScope.values()), oAuthScopes);
    }

    @Nonnull
    @Override
    public ExternalUser getUser(ExternalRequest request) {
        return requestExecutor.executeGetRequest(
                request.getCredential(),
                buildUserUrl(bbCloudApiUrl, request.getCredential().getUsername()),
                response -> {
                    String responseEntity = getResponseEntity(response);
                    try {
                        return ValidationUtils.validate(validator,
                                new ObjectMapper().readValue(responseEntity, BitbucketCloudOwner.class));
                    } catch (IOException e) {
                        throw newExternalRequestFailedException();
                    }
                });
    }

    @Nonnull
    @Override
    public Page<ExternalOwner> searchOwners(@Nonnull ExternalOwnerSearchRequest request, @Nonnull PageRequest pageRequest) {
        requireNonNull(request, "request");
        requireNonNull(pageRequest, "pageRequest");
        Page<BitbucketCloudOwner> page = requestExecutor.executeGetRequest(
                request.getCredential(),
                buildOwnerSearchUrl(bbCloudApiUrl, pageRequest),
                response -> {
                    String responseEntity = getResponseEntity(response);
                    try {
                        BitbucketCloudOwnerResponse ownerResponse = ValidationUtils.validate(validator,
                                new ObjectMapper().readValue(responseEntity, BitbucketCloudOwnerResponse.class));
                        if (ownerResponse == null || ownerResponse.isEmpty()) {
                            throw newExternalRequestFailedException();
                        }
                        List<BitbucketCloudOwner> owners = ownerResponse
                                .getValues()
                                .stream()
                                .filter(Objects::nonNull)
                                .collect(toList());
                        return PageUtils.createPage(owners, ownerResponse.isLastPage(), pageRequest);
                    } catch (IOException e) {
                        log.warn("Failed to deserialize response from the external repository source", e);
                        throw newExternalRequestFailedException();
                    }
                });
        return PageUtils.asPageOf(ExternalOwner.class, page);
    }

    @Nonnull
    @Override
    public Page<ExternalRepository> searchRepositories(@Nonnull ExternalRepositorySearchRequest request,
                                                       @Nonnull PageRequest pageRequest) {
        requireNonNull(request, "request");
        requireNonNull(pageRequest, "pageRequest");
        Credential credential = request.getCredential();

        Page<BitbucketCloudRepository> page = requestExecutor.executeGetRequest(
                credential,
                buildRepositoriesSearchUrl(bbCloudApiUrl, request, pageRequest),
                response -> {
                    String responseEntity = getResponseEntity(response);
                    try {
                        BitbucketCloudRepositoryResponse repositoryResponse = ValidationUtils.validate(validator,
                                new ObjectMapper().readValue(responseEntity, BitbucketCloudRepositoryResponse.class));
                        if (repositoryResponse == null || repositoryResponse.isEmpty()) {
                            throw newExternalRequestFailedException();
                        }
                        List<BitbucketCloudRepository> repositories = repositoryResponse
                                .getValues()
                                .stream()
                                .filter(Objects::nonNull)
                                .collect(toList());
                        return PageUtils.createPage(repositories, repositoryResponse.isLastPage(), pageRequest);
                    } catch (IOException e) {
                        log.warn("Failed to deserialize response from the external repository source", e);
                        throw newExternalRequestFailedException();
                    }
                });

        return PageUtils.asPageOf(ExternalRepository.class, page);
    }

    private String getResponseEntity(Response response) {
        String responseEntity = response.getEntity();
        if (responseEntity == null || responseEntity.isEmpty()) {
            newExternalRequestFailedException();
        }
        return responseEntity;
    }

    private ExternalRequestFailedException newExternalRequestFailedException() {
        throw new ExternalRequestFailedException(
                i18nService.createKeyedMessage("bitbucket.importer.external.request.failed"));
    }
}
