/****************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one   *
 * or more contributor license agreements.  See the NOTICE file *
 * distributed with this work for additional information        *
 * regarding copyright ownership.  The ASF licenses this file   *
 * to you 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.james.webadmin.routes;

import static org.apache.james.webadmin.Constants.SEPARATOR;
import static spark.Spark.halt;

import java.util.Comparator;

import javax.inject.Inject;

import org.apache.james.core.Domain;
import org.apache.james.core.MailAddress;
import org.apache.james.core.Username;
import org.apache.james.domainlist.api.DomainList;
import org.apache.james.domainlist.api.DomainListException;
import org.apache.james.rrt.api.LoopDetectedException;
import org.apache.james.rrt.api.MappingAlreadyExistsException;
import org.apache.james.rrt.api.MappingConflictException;
import org.apache.james.rrt.api.RecipientRewriteTable;
import org.apache.james.rrt.api.RecipientRewriteTableException;
import org.apache.james.rrt.api.SameSourceAndDestinationException;
import org.apache.james.rrt.api.SourceDomainIsNotInDomainListException;
import org.apache.james.rrt.lib.Mapping;
import org.apache.james.rrt.lib.MappingSource;
import org.apache.james.webadmin.Routes;
import org.apache.james.webadmin.dto.AliasSourcesResponse;
import org.apache.james.webadmin.utils.ErrorResponder;
import org.apache.james.webadmin.utils.JsonTransformer;
import org.eclipse.jetty.http.HttpStatus;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;

import spark.HaltException;
import spark.Request;
import spark.Response;
import spark.Service;

public class AliasRoutes implements Routes {

    public static final String ROOT_PATH = "address/aliases";

    private static final String ALIAS_DESTINATION_ADDRESS = "aliasDestinationAddress";
    private static final String ALIAS_ADDRESS_PATH = ROOT_PATH + SEPARATOR + ":" + ALIAS_DESTINATION_ADDRESS;
    private static final String ALIAS_SOURCE_ADDRESS = "aliasSourceAddress";
    private static final String USER_IN_ALIAS_SOURCES_ADDRESSES_PATH = ALIAS_ADDRESS_PATH + SEPARATOR +
        "sources" + SEPARATOR + ":" + ALIAS_SOURCE_ADDRESS;
    private static final String ADDRESS_TYPE = "alias";

    private final DomainList domainList;
    private final JsonTransformer jsonTransformer;
    private final RecipientRewriteTable recipientRewriteTable;

    @Inject
    @VisibleForTesting
    AliasRoutes(RecipientRewriteTable recipientRewriteTable, DomainList domainList, JsonTransformer jsonTransformer) {
        this.domainList = domainList;
        this.jsonTransformer = jsonTransformer;
        this.recipientRewriteTable = recipientRewriteTable;
    }

    @Override
    public String getBasePath() {
        return ROOT_PATH;
    }

    @Override
    public void define(Service service) {
        service.get(ROOT_PATH, this::listAddressesWithAliases, jsonTransformer);
        service.get(ALIAS_ADDRESS_PATH, this::listAliasesOfAddress, jsonTransformer);
        service.put(USER_IN_ALIAS_SOURCES_ADDRESSES_PATH, this::addAlias);
        service.delete(USER_IN_ALIAS_SOURCES_ADDRESSES_PATH, this::deleteAlias);
    }

    public ImmutableSet<String> listAddressesWithAliases(Request request, Response response) throws RecipientRewriteTableException {
        return recipientRewriteTable.getMappingsForType(Mapping.Type.Alias)
            .flatMap(mapping -> mapping.asMailAddress().stream())
            .map(MailAddress::asString)
            .collect(ImmutableSortedSet.toImmutableSortedSet(String::compareTo));
    }

    public HaltException addAlias(Request request, Response response) throws Exception {
        MailAddress aliasSourceAddress = MailAddressParser.parseMailAddress(request.params(ALIAS_SOURCE_ADDRESS), ADDRESS_TYPE);
        MailAddress destinationAddress = MailAddressParser.parseMailAddress(request.params(ALIAS_DESTINATION_ADDRESS), ADDRESS_TYPE);
        ensureDomainIsSupported(destinationAddress.getDomain());
        MappingSource source = MappingSource.fromUser(Username.fromMailAddress(aliasSourceAddress));
        addAlias(source, destinationAddress);
        return halt(HttpStatus.NO_CONTENT_204);
    }

    private void addAlias(MappingSource source, MailAddress aliasSourceAddress) throws RecipientRewriteTableException {
        try {
            recipientRewriteTable.addAliasMapping(source, aliasSourceAddress.asString());
        } catch (MappingAlreadyExistsException e) {
            // ignore
        } catch (SameSourceAndDestinationException | SourceDomainIsNotInDomainListException e) {
            throw ErrorResponder.builder()
                .statusCode(HttpStatus.BAD_REQUEST_400)
                .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
                .message(e.getMessage())
                .haltError();
        } catch (LoopDetectedException | MappingConflictException e) {
            throw ErrorResponder.builder()
                .statusCode(HttpStatus.CONFLICT_409)
                .type(ErrorResponder.ErrorType.WRONG_STATE)
                .message(e.getMessage())
                .haltError();
        }
    }

    private void ensureDomainIsSupported(Domain domain) throws DomainListException {
        if (!domainList.containsDomain(domain)) {
            throw ErrorResponder.builder()
                .statusCode(HttpStatus.BAD_REQUEST_400)
                .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
                .message("Domain in the destination is not managed by the DomainList")
                .haltError();
        }
    }

    public HaltException deleteAlias(Request request, Response response) throws RecipientRewriteTableException {
        MailAddress destinationAddress = MailAddressParser.parseMailAddress(request.params(ALIAS_DESTINATION_ADDRESS), ADDRESS_TYPE);
        MailAddress aliasToBeRemoved = MailAddressParser.parseMailAddress(request.params(ALIAS_SOURCE_ADDRESS), ADDRESS_TYPE);
        MappingSource source = MappingSource.fromMailAddress(aliasToBeRemoved);
        recipientRewriteTable.removeAliasMapping(source, destinationAddress.asString());
        return halt(HttpStatus.NO_CONTENT_204);
    }

    public ImmutableSet<AliasSourcesResponse> listAliasesOfAddress(Request request, Response response) throws RecipientRewriteTableException {
        MailAddress destinationAddress = MailAddressParser.parseMailAddress(request.params(ALIAS_DESTINATION_ADDRESS), ADDRESS_TYPE);

        return recipientRewriteTable.listSources(Mapping.alias(destinationAddress.asString()))
            .sorted(Comparator.comparing(MappingSource::asMailAddressString))
            .map(AliasSourcesResponse::new)
            .collect(ImmutableSet.toImmutableSet());
    }
}
