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

import com.atlassian.bitbucket.internal.importer.*;
import com.atlassian.bitbucket.rest.RestErrorMessage;
import com.atlassian.bitbucket.rest.RestErrors;
import com.atlassian.bitbucket.rest.util.ResponseFactory;
import com.atlassian.bitbucket.rest.util.RestPage;
import com.atlassian.bitbucket.rest.util.RestUtils;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.sun.jersey.spi.resource.Singleton;
import org.springframework.beans.factory.annotation.Qualifier;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.function.Supplier;

import static com.atlassian.bitbucket.internal.importer.rest.ResponseError.*;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;

@Path("/external-source")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({RestUtils.APPLICATION_JSON_UTF8})
@Singleton
public class ExternalRepositoryResource {

    private final ExternalRepositoryService externalRepositoryService;
    private final ResourceHelper resourceHelper;

    public ExternalRepositoryResource(@Qualifier("externalRepositoryService") ExternalRepositoryService externalRepositoryService,
                                      @Qualifier("resourceHelper") ResourceHelper resourceHelper) {
        this.externalRepositoryService = externalRepositoryService;
        this.resourceHelper = resourceHelper;
    }

    @Path("/missing-oauth-scopes")
    @POST
    public Response getMissingOAuthScopes(RestExternalRequest request) {
        List<String> errors = resourceHelper.validate(request);
        if (!errors.isEmpty()) {
            return resourceHelper.restRequestError(errors);
        }

        ExternalRequest userRequest = new ExternalRequest
                .Builder(resourceHelper.credential(request), resourceHelper.source(request))
                .build();
        return executeRequest(() -> {
            Iterable<OAuthScope> missingOAuthScopes = externalRepositoryService.getMissingOAuthScopes(userRequest);
            return ResponseFactory.ok(missingOAuthScopes).build();
        });
    }

    @Path("/user")
    @POST
    public Response getUser(RestExternalRequest request) {
        List<String> errors = resourceHelper.validate(request);
        if (!errors.isEmpty()) {
            return resourceHelper.restRequestError(errors);
        }

        ExternalRequest userRequest = new ExternalRequest
                .Builder(resourceHelper.credential(request), resourceHelper.source(request))
                .build();
        return executeRequest(() -> {
            ExternalUser user = externalRepositoryService.getUser(userRequest);
            return ResponseFactory.ok(new RestExternalUser(user)).build();
        });
    }

    @Path("/owners")
    @POST
    public Response searchOwners(RestExternalOwnerSearchRequest request,
                                 @Context PageRequest pageRequest) {
        List<String> errors = resourceHelper.validate(request);
        if (!errors.isEmpty()) {
            return resourceHelper.restRequestError(errors);
        }

        ExternalOwnerSearchRequest searchRequest = new ExternalOwnerSearchRequest
                .Builder(resourceHelper.credential(request), resourceHelper.source(request))
                .build();
        return executeRequest(() -> {
            Page<ExternalOwner> owners = externalRepositoryService.searchOwners(searchRequest, pageRequest);
            return ResponseFactory.ok(new RestPage<>(owners, owner -> new RestExternalOwner(owner))).build();
        });
    }

    @Path("/repos")
    @POST
    public Response searchRepositories(RestExternalRepositorySearchRequest request,
                                       @Context PageRequest pageRequest) {
        List<String> errors = resourceHelper.validate(request);
        if (!errors.isEmpty()) {
            return resourceHelper.restRequestError(errors);
        }

        ExternalRepositorySearchRequest builder = new ExternalRepositorySearchRequest
                .Builder(resourceHelper.credential(request), resourceHelper.source(request))
                .filter(request.getFilter())
                .ownerName(request.getOwnerName())
                .build();
        return executeRequest(() -> {
            Page<ExternalRepository> repositories = externalRepositoryService.searchRepositories(builder, pageRequest);
            return ResponseFactory.ok(new RestPage<>(repositories, RestExternalRepository::new)).build();
        });
    }

    private Response executeRequest(Supplier<Response> supplier) {
        try {
            return supplier.get();
        } catch (ExternalRequestAuthenticationFailedException e) {
            return externalRequestError(AUTHENTICATION, e);
        } catch (ExternalRequestForbiddenException e) {
            return externalRequestError(FORBIDDEN, e);
        } catch (ExternalRequestFailedException e) {
            return externalRequestError(EXTERNAL_REQUEST, e);
        } catch (UnsupportedSourceException e) {
            return externalRequestError(UNSUPPORTED_SOURCE, e);
        }
    }

    private Response externalRequestError(ResponseError context, Exception e) {
        return ResponseFactory.errors(BAD_REQUEST,
                new RestErrors(new RestErrorMessage(context.name(), e)))
                .build();
    }
}
