//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.apache.couchdb.nouveau.resources;

import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Metered;
import com.codahale.metrics.annotation.ResponseMetered;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response.Status;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import org.apache.couchdb.nouveau.api.BulkUpdateRequest;
import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
import org.apache.couchdb.nouveau.api.IndexDefinition;
import org.apache.couchdb.nouveau.api.IndexInfoRequest;
import org.apache.couchdb.nouveau.api.IndexInfoResponse;
import org.apache.couchdb.nouveau.api.Ok;
import org.apache.couchdb.nouveau.api.SearchRequest;
import org.apache.couchdb.nouveau.api.SearchResults;
import org.apache.couchdb.nouveau.core.IndexManager;

@Path("/index/{name}")
@Metered
@ResponseMetered
@ExceptionMetered(cause = IOException.class)
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public final class IndexResource {

    private final IndexManager indexManager;

    public IndexResource(final IndexManager indexManager) {
        this.indexManager = Objects.requireNonNull(indexManager);
    }

    @PUT
    public Ok createIndex(@PathParam("name") String name, @NotNull @Valid IndexDefinition indexDefinition)
            throws IOException {
        if (!indexDefinition.isLatestVersion()) {
            throw new WebApplicationException(
                    "Cannot create a new version " + indexDefinition.luceneVersionAsInt() + " index",
                    Status.BAD_REQUEST);
        }
        indexManager.create(name, indexDefinition);
        return Ok.INSTANCE;
    }

    @Deprecated(since = "3.5.2", forRemoval = true)
    @DELETE
    @Path("/doc/{docId}")
    public Ok deleteDoc(
            @PathParam("name") String name,
            @PathParam("docId") String docId,
            @NotNull @Valid DocumentDeleteRequest request)
            throws Exception {
        return indexManager.with(name, (index) -> {
            index.delete(docId, request);
            return Ok.INSTANCE;
        });
    }

    @DELETE
    public Ok deletePath(@PathParam("name") String path, @Valid final List<String> exclusions) throws IOException {
        indexManager.deleteAll(path, exclusions);
        return Ok.INSTANCE;
    }

    @GET
    public IndexInfoResponse getIndexInfo(@PathParam("name") String name) throws Exception {
        return indexManager.with(name, (index) -> {
            return index.info();
        });
    }

    @POST
    public Ok setIndexInfo(@PathParam("name") String name, @NotNull @Valid IndexInfoRequest request) throws Exception {
        return indexManager.with(name, (index) -> {
            var shouldCommit = false;
            if (request.matchUpdateSeq().isPresent() && request.updateSeq().isPresent()) {
                index.setUpdateSeq(
                        request.matchUpdateSeq().getAsLong(),
                        request.updateSeq().getAsLong());
                shouldCommit = true;
            }
            if (request.matchPurgeSeq().isPresent() && request.purgeSeq().isPresent()) {
                index.setPurgeSeq(
                        request.matchPurgeSeq().getAsLong(), request.purgeSeq().getAsLong());
                shouldCommit = true;
            }
            if (shouldCommit && request.shouldCommit()) {
                index.commit();
            }
            return Ok.INSTANCE;
        });
    }

    @POST
    @Path("/search")
    public SearchResults searchIndex(@PathParam("name") String name, @NotNull @Valid SearchRequest request)
            throws Exception {
        return indexManager.with(name, (index) -> {
            return index.search(request);
        });
    }

    @Deprecated(since = "3.5.2", forRemoval = true)
    @PUT
    @Path("/doc/{docId}")
    public Ok updateDoc(
            @PathParam("name") String name,
            @PathParam("docId") String docId,
            @NotNull @Valid DocumentUpdateRequest request)
            throws Exception {
        return indexManager.with(name, (index) -> {
            index.update(docId, request);
            return Ok.INSTANCE;
        });
    }

    @POST
    @Path("/update")
    public Ok update(@PathParam("name") String name, @NotNull @Valid BulkUpdateRequest request) throws Exception {
        return indexManager.with(name, (index) -> {
            for (var update : request.updates()) {
                if (update.update() instanceof DocumentUpdateRequest) {
                    index.update(update.docId(), (DocumentUpdateRequest) update.update());
                }
                if (update.update() instanceof DocumentDeleteRequest) {
                    index.delete(update.docId(), (DocumentDeleteRequest) update.update());
                }
            }
            return Ok.INSTANCE;
        });
    }
}
