package br.com.conductor.heimdall.core.service;

/*-
 * =========================LICENSE_START==================================
 * heimdall-core
 * ========================================================================
 * Copyright (C) 2018 Conductor Tecnologia SA
 * ========================================================================
 * 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.
 * ==========================LICENSE_END===================================
 */

import static br.com.conductor.heimdall.core.exception.ExceptionMessage.ENVIRONMENT_INBOUND_DNS_PATTERN;
import static br.com.conductor.heimdall.core.exception.ExceptionMessage.GLOBAL_RESOURCE_NOT_FOUND;
import static br.com.conductor.heimdall.core.exception.ExceptionMessage.ENVIRONMENT_ATTACHED_TO_API;

import java.util.List;
import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import br.com.conductor.heimdall.core.converter.GenericConverter;
import br.com.conductor.heimdall.core.dto.EnvironmentDTO;
import br.com.conductor.heimdall.core.dto.PageDTO;
import br.com.conductor.heimdall.core.dto.PageableDTO;
import br.com.conductor.heimdall.core.dto.page.EnvironmentPage;
import br.com.conductor.heimdall.core.entity.Environment;
import br.com.conductor.heimdall.core.exception.ExceptionMessage;
import br.com.conductor.heimdall.core.exception.HeimdallException;
import br.com.conductor.heimdall.core.repository.EnvironmentRepository;
import br.com.conductor.heimdall.core.util.Pageable;

/**
 * This class provides methods to create, read, update and delete the {@link Environment} resource.
 *
 * @author Filipe Germano
 * @author <a href="https://dijalmasilva.github.io" target="_blank">Dijalma Silva</a>
 */
@Service
public class EnvironmentService {

    @Autowired
    private EnvironmentRepository environmentRepository;

    /**
     * Finds a {@link Environment} by its ID.
     *
     * @param id The id of the {@link Environment}
     * @return The {@link Environment} that was found
     */
    public Environment find(Long id) {

        Environment environment = environmentRepository.findOne(id);
        HeimdallException.checkThrow(environment == null, GLOBAL_RESOURCE_NOT_FOUND);

        return environment;
    }

    /**
     * Generates a paged list of {@link Environment} from a request.
     *
     * @param environmentDTO The {@link EnvironmentDTO}
     * @param pageableDTO    The {@link PageableDTO}
     * @return The paged {@link Environment} list as a {@link EnvironmentPage} object
     */
    public EnvironmentPage list(EnvironmentDTO environmentDTO, PageableDTO pageableDTO) {

        Environment environment = GenericConverter.mapper(environmentDTO, Environment.class);

        Example<Environment> example = Example.of(environment, ExampleMatcher.matching().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING));

        Pageable pageable = Pageable.setPageable(pageableDTO.getOffset(), pageableDTO.getLimit());
        Page<Environment> page = environmentRepository.findAll(example, pageable);

         return new EnvironmentPage(PageDTO.build(page));
    }

    /**
     * Generates a list of {@link Environment} from a request.
     *
     * @param environmentDTO The {@link EnvironmentDTO}
     * @return The List<{@link Environment}>
     */
    public List<Environment> list(EnvironmentDTO environmentDTO) {

        Environment environment = GenericConverter.mapper(environmentDTO, Environment.class);

        Example<Environment> example = Example.of(environment, ExampleMatcher.matching().withIgnoreCase().withStringMatcher(StringMatcher.CONTAINING));

         return environmentRepository.findAll(example);
    }

    /**
     * Saves a {@link Environment} in the repository.
     *
     * @param environmentDTO The {@link EnvironmentDTO}
     * @return The saved {@link Environment}
     */
    @Transactional
    public Environment save(EnvironmentDTO environmentDTO) {

        List<Environment> environments = environmentRepository.findByInboundURL(environmentDTO.getInboundURL());

        Environment environmentEqual = environments.stream().filter(e -> e.getOutboundURL().equals(environmentDTO.getOutboundURL())).findFirst().orElse(null);
        HeimdallException.checkThrow(environmentEqual != null, ExceptionMessage.ENVIRONMENT_ALREADY_EXISTS);

        Environment environment = GenericConverter.mapper(environmentDTO, Environment.class);
        HeimdallException.checkThrow(!validateInboundURL(environment.getInboundURL()), ENVIRONMENT_INBOUND_DNS_PATTERN);

        environment = environmentRepository.save(environment);

        return environment;
    }

    /**
     * Updates a {@link Environment} by its ID.
     *
     * @param id             The id of the {@link Environment}
     * @param environmentDTO The {@link EnvironmentDTO}
     * @return The updated {@link Environment}
     */
    @Transactional
    public Environment update(Long id, EnvironmentDTO environmentDTO) {

        Environment environment = environmentRepository.findOne(id);
        HeimdallException.checkThrow(environment == null, GLOBAL_RESOURCE_NOT_FOUND);

        List<Environment> environments = environmentRepository.findByInboundURL(environmentDTO.getInboundURL());

        Environment environmentEqual = environments.stream().filter(e -> e.getOutboundURL().equals(environmentDTO.getOutboundURL())).findFirst().orElse(null);
        HeimdallException.checkThrow(environmentEqual != null && !Objects.requireNonNull(environmentEqual).getId().equals(environment.getId()), ExceptionMessage.ENVIRONMENT_ALREADY_EXISTS);

        Integer apis = environmentRepository.findApiWithOtherEnvironmentEqualsInbound(environment.getId(), environmentDTO.getInboundURL());
        HeimdallException.checkThrow(apis > 0, ExceptionMessage.API_CANT_ENVIRONMENT_INBOUND_URL_EQUALS);

        environment = GenericConverter.mapper(environmentDTO, environment);
        HeimdallException.checkThrow(!validateInboundURL(environment.getInboundURL()), ENVIRONMENT_INBOUND_DNS_PATTERN);

        environmentRepository.save(environment);

        return environment;
    }

     /**
      * Deletes a {@link Environment} by its ID.
      *
      * @param 	id 						The id of the {@link Environment}
      */
     @Transactional
     public void delete(Long id) {

        Environment environment = environmentRepository.findOne(id);
        HeimdallException.checkThrow(environment == null, GLOBAL_RESOURCE_NOT_FOUND);

        Integer totalEnvironmentsAttached = environmentRepository.findApisWithEnvironment(id);
        HeimdallException.checkThrow(totalEnvironmentsAttached > 0, ENVIRONMENT_ATTACHED_TO_API);


        environmentRepository.delete(environment);
    }

     /*
      * Validates if the String follow one of the patterns:
      *     * http[s]://host.domain[:port]
      *     * www.host.domain[:port]
      *
      * @return true if it is a a valid inbound, false otherwise
      */
    private boolean validateInboundURL(String inbound) {

        if (inbound.matches("(http://|https://|www\\.)(.+)")) {
            String temp = inbound.replaceAll("(http://|https://|www\\.)(.+)", "$2");

            if (temp.matches(".+\\..+"))
                return !temp.matches(".*/.*");
        }

        return false;
    }

}