package controllers.api;

import com.fasterxml.jackson.core.JsonProcessingException;
import controllers.App;
import play.libs.F;
import play.mvc.BodyParser;
import play.mvc.Controller;
import play.mvc.Result;
import uk.gov.openregister.domain.Record;
import uk.gov.openregister.domain.RecordVersionInfo;
import uk.gov.openregister.store.DatabaseException;
import uk.gov.openregister.store.Store;
import uk.gov.openregister.validation.ValidationError;
import uk.gov.openregister.validation.Validator;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

import static controllers.api.Representations.representationFor;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toMap;

public class Rest extends Controller {

    public static final String REPRESENTATION_QUERY_PARAM = "_representation";

    private final Store store;
    private final List<String> fieldNames;
    private final String registerName;

    public Rest() {
        store = App.instance.register.store();
        fieldNames = App.instance.register.fieldNames();
        registerName = App.instance.register.name();
    }

    @BodyParser.Of(BodyParser.Json.class)
    public Result create() throws JsonProcessingException {
        Record r = new Record(request().body().asJson());

        List<ValidationError> validationErrors = new Validator(singletonList(registerName), fieldNames).validate(r);

        if (validationErrors.isEmpty()) {
            try {
                store.save(r);
            } catch (DatabaseException e) {
                return JsonRepresentation.instance.toResponse(400, e.getMessage());
            }

            return JsonRepresentation.instance.toResponse(202, "Record saved successfully");
        }

        return JsonRepresentation.instance.toResponseWithErrors(400, "", validationErrors);

    }

    @BodyParser.Of(BodyParser.Json.class)
    public Result update(String hash) {
        Record r = new Record(request().body().asJson());
        List<ValidationError> validationErrors = new Validator(singletonList(registerName), fieldNames).validate(r);

        if (validationErrors.isEmpty()) {
            try {
                store.update(hash, r);
            } catch (DatabaseException e) {
                return JsonRepresentation.instance.toResponse(400, e.getMessage());
            }
            return JsonRepresentation.instance.toResponse(202, "Record saved successfully");
        }

        return JsonRepresentation.instance.toResponseWithErrors(400, "", validationErrors);
    }

    public F.Promise<Result> findByKey(String key, String value) {
        return findByKeyWithFormat(key, value, representationQueryString());
    }

    public F.Promise<Result> findByKeyWithFormat(String key, String value, String format) {
        F.Promise<Optional<Record>> recordF = F.Promise.promise(() -> store.findByKV(key, value));
        return recordF.map(record -> getResponse(record, format,
                anyFormat -> controllers.api.routes.Rest.findByKeyWithFormat(key, value, anyFormat).url()));
    }

    public F.Promise<Result> findByHash(String hash) {
        return findByHashWithFormat(hash, representationQueryString());
    }

    public F.Promise<Result> findByHashWithFormat(String hash, String format) {
        F.Promise<Optional<Record>> recordF = F.Promise.promise(() -> store.findByHash(hash));
        return recordF.map(record -> getResponse(record, format,
                anyFormat -> controllers.api.routes.Rest.findByHashWithFormat(hash, anyFormat).url()));
    }

    private String representationQueryString() {
        return request().getQueryString(REPRESENTATION_QUERY_PARAM);
    }

    private Result getResponse(Optional<Record> recordO, String format, Function<String, String> routeForFormat) {
        final Representation representation;
        try {
            representation = representationFor(format);
        } catch (IllegalArgumentException e) {
            return formatNotRecognisedResponse(format);
        }
        return recordO.map(record ->
                        representation.toRecord(record, getHistoryFor(record), representationsMap(routeForFormat))
        ).orElse(HtmlRepresentation.instance.toResponse(404, "Entry not found"));
    }

    public F.Promise<Result> search() {
        Representation representation;
        try {
            representation = representationFor(representationQueryString());
        } catch (IllegalArgumentException e) {
            return F.Promise.pure(formatNotRecognisedResponse(representationQueryString()));
        }

        F.Promise<List<Record>> recordsF = F.Promise.promise(() -> {
            if (request().queryString().containsKey("_query")) {
                return store.search(request().queryString().get("_query")[0]);
            } else {
                Map<String, String> map = request().queryString().entrySet().stream()
                        .filter(queryParameter -> !queryParameter.getKey().startsWith("_"))
                        .collect(toMap(Map.Entry::getKey, queryParamEntry -> queryParamEntry.getValue()[0]));
                return store.search(map);
            }
        });

        return recordsF.map(rs -> representation.toListOfRecords(rs, representationsMap(representationsBaseUri())));
    }

    private Result formatNotRecognisedResponse(String format) {
        return HtmlRepresentation.instance.toResponse(400, "Format '" + format + "' not recognised");
    }

    private List<RecordVersionInfo> getHistoryFor(Record r) {
        return store.history(registerName, r.getEntry().get(registerName).textValue());
    }

    private Map<String, String> representationsMap(Function<String, String> routeForFormat) {
        return Stream.of(Representations.Format.values())
                .map(Representations.Format::name)
                .collect(toMap(Function.<String>identity(),
                        routeForFormat));
    }

    private Map<String, String> representationsMap(String representationsBaseUri) {
        final Map<String, String> representationMap = new HashMap<>();
        for (Representations.Format format : Representations.Format.values()) {
            representationMap.put(format.name(), representationsBaseUri + format.name());
        }

        return representationMap;
    }

    private String representationsBaseUri() {
        String rawRepresentationUri = request().uri();
        StringBuilder representationUri = new StringBuilder(rawRepresentationUri);

        if (!rawRepresentationUri.contains("?")) {
            representationUri.append("?" + REPRESENTATION_QUERY_PARAM + "=");
        } else {
            representationUri.append("&" + REPRESENTATION_QUERY_PARAM + "=");
        }

        return representationUri.toString();
    }
}

@@ -498,34 +498,8 @@
 r;%0A%0A
-import java.util.HashMap;%0A
 impo
@@ -5359,32 +5359,32 @@
 Map(
-representationsBaseUri()
+this::searchUriForFormat
 )));
@@ -6049,836 +6049,191 @@
 ate 
-Map%3C
 String
-, String%3E representationsMap(String representationsBaseUri) %7B%0A        final Map%3CString, String%3E representationMap = new HashMap%3C%3E();%0A        for (Representations.Format format : Representations.Format.values()) %7B%0A            representationMap.put(format.name(), representationsBaseUri + format.name());%0A        %7D%0A%0A        return representationMap;%0A    %7D%0A%0A    private String representationsBaseUri() %7B%0A        String rawRepresentationUri = request().uri();%0A        StringBuilder representationUri = new StringBuilder(rawRepresentationUri);%0A%0A        if (!rawRepresentationUri.contains(%22?%22)) %7B%0A            representationUri.append(%22?%22 + REPRESENTATION_QUERY_PARAM + %22=%22);%0A        %7D else %7B%0A            representationUri.append(%22&%22 + REPRESENTATION_QUERY_PARAM + %22=%22);%0A        %7D%0A%0A        return representationUri.toString()
+ searchUriForFormat(String format) %7B%0A        // makes assumptions about the structure of the uri%0A        return request().uri() + %22&%22 + REPRESENTATION_QUERY_PARAM + %22=%22 + format
 ;%0A  

Use new representationsMap() method everywhere