package org.apache.archiva.admin.repository.remote;
/*
 * 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.
 */

import org.apache.archiva.admin.model.AuditInformation;
import org.apache.archiva.admin.model.RepositoryAdminException;
import org.apache.archiva.admin.model.beans.RemoteRepository;
import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin;
import org.apache.archiva.admin.repository.AbstractRepositoryAdmin;
import org.apache.archiva.common.plexusbridge.MavenIndexerUtils;
import org.apache.archiva.common.plexusbridge.PlexusSisuBridge;
import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
import org.apache.archiva.configuration.Configuration;
import org.apache.archiva.configuration.ProxyConnectorConfiguration;
import org.apache.archiva.configuration.RemoteRepositoryConfiguration;
import org.apache.archiva.configuration.RepositoryCheckPath;
import org.apache.archiva.metadata.model.facets.AuditEvent;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.index.NexusIndexer;
import org.apache.maven.index.context.IndexCreator;
import org.apache.maven.index.context.IndexingContext;
import org.apache.maven.index.context.UnsupportedExistingLuceneIndexException;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Olivier Lamy
 * @since 1.4-M1
 */
@Service("remoteRepositoryAdmin#default")
public class DefaultRemoteRepositoryAdmin
    extends AbstractRepositoryAdmin
    implements RemoteRepositoryAdmin
{

    @Inject
    private PlexusSisuBridge plexusSisuBridge;

    @Inject
    private MavenIndexerUtils mavenIndexerUtils;

    // fields
    private List<? extends IndexCreator> indexCreators;

    private NexusIndexer indexer;

    @PostConstruct
    private void initialize()
        throws RepositoryAdminException
    {
        try
        {
            indexCreators = mavenIndexerUtils.getAllIndexCreators();
            indexer = plexusSisuBridge.lookup( NexusIndexer.class );
        }
        catch ( PlexusSisuBridgeException e )
        {
            throw new RepositoryAdminException( e.getMessage(), e );
        }
        for ( RemoteRepository remoteRepository : getRemoteRepositories() )
        {
            createIndexContext( remoteRepository );
        }
    }

    @PreDestroy
    private void shutdown()
        throws RepositoryAdminException
    {
        try
        {
            List<RemoteRepository> remoteRepositories = getRemoteRepositories();
            // close index on shutdown
            for ( RemoteRepository remoteRepository : remoteRepositories )
            {
                IndexingContext context = indexer.getIndexingContexts().get( remoteRepository.getId() );
                if ( context != null )
                {
                    indexer.removeIndexingContext( context, false );
                }
            }
        }
        catch ( IOException e )
        {
            throw new RepositoryAdminException( e.getMessage(), e );
        }
    }


    @Override
    public List<RemoteRepository> getRemoteRepositories()
        throws RepositoryAdminException
    {
        List<RemoteRepository> remoteRepositories =
            new ArrayList<>( getArchivaConfiguration().getConfiguration().getRemoteRepositories().size() );
        for ( RemoteRepositoryConfiguration repositoryConfiguration : getArchivaConfiguration().getConfiguration().getRemoteRepositories() )
        {
            RemoteRepository remoteRepository =
                new RemoteRepository( repositoryConfiguration.getId(), repositoryConfiguration.getName(),
                                      repositoryConfiguration.getUrl(), repositoryConfiguration.getLayout(),
                                      repositoryConfiguration.getUsername(), repositoryConfiguration.getPassword(),
                                      repositoryConfiguration.getTimeout() );
            remoteRepository.setDownloadRemoteIndex( repositoryConfiguration.isDownloadRemoteIndex() );
            remoteRepository.setRemoteIndexUrl( repositoryConfiguration.getRemoteIndexUrl() );
            remoteRepository.setCronExpression( repositoryConfiguration.getRefreshCronExpression() );
            remoteRepository.setIndexDirectory( repositoryConfiguration.getIndexDir() );
            remoteRepository.setRemoteDownloadNetworkProxyId(
                repositoryConfiguration.getRemoteDownloadNetworkProxyId() );
            remoteRepository.setRemoteDownloadTimeout( repositoryConfiguration.getRemoteDownloadTimeout() );
            remoteRepository.setDownloadRemoteIndexOnStartup(
                repositoryConfiguration.isDownloadRemoteIndexOnStartup() );
            remoteRepository.setDescription( repositoryConfiguration.getDescription() );
            remoteRepository.setExtraHeaders( repositoryConfiguration.getExtraHeaders() );
            remoteRepository.setExtraParameters( repositoryConfiguration.getExtraParameters() );
            remoteRepository.setCheckPath(repositoryConfiguration.getCheckPath());
            remoteRepositories.add( remoteRepository );
        }
        return remoteRepositories;
    }

    @Override
    public RemoteRepository getRemoteRepository( String repositoryId )
        throws RepositoryAdminException
    {
        for ( RemoteRepository remoteRepository : getRemoteRepositories() )
        {
            if ( StringUtils.equals( repositoryId, remoteRepository.getId() ) )
            {
                return remoteRepository;
            }
        }
        return null;
    }

    @Override
    public Boolean addRemoteRepository( RemoteRepository remoteRepository, AuditInformation auditInformation )
        throws RepositoryAdminException
    {
        triggerAuditEvent( remoteRepository.getId(), null, AuditEvent.ADD_REMOTE_REPO, auditInformation );
        getRepositoryCommonValidator().basicValidation( remoteRepository, false );

        //TODO we can validate it's a good uri/url
        if ( StringUtils.isEmpty( remoteRepository.getUrl() ) )
        {
            throw new RepositoryAdminException( "url cannot be null" );
        }

        //MRM-752 - url needs trimming
        //MRM-1940 - URL should not end with a slash
        remoteRepository.setUrl( StringUtils.stripEnd(StringUtils.trim( remoteRepository.getUrl() ), "/"));

        if (StringUtils.isEmpty(remoteRepository.getCheckPath())) {
            String checkUrl = remoteRepository.getUrl().toLowerCase();
            for (RepositoryCheckPath path : getArchivaConfiguration ().getConfiguration().getArchivaDefaultConfiguration().getDefaultCheckPaths()) {
                log.debug("Checking path for urls: {} <-> {}", checkUrl, path.getUrl());
                if (checkUrl.startsWith(path.getUrl())) {
                    remoteRepository.setCheckPath(path.getPath());
                    break;
                }
            }
        }

        RemoteRepositoryConfiguration remoteRepositoryConfiguration =
            getRemoteRepositoryConfiguration( remoteRepository );

        Configuration configuration = getArchivaConfiguration().getConfiguration();
        configuration.addRemoteRepository( remoteRepositoryConfiguration );
        saveConfiguration( configuration );

        return Boolean.TRUE;
    }

    @Override
    public Boolean deleteRemoteRepository( String repositoryId, AuditInformation auditInformation )
        throws RepositoryAdminException
    {

        triggerAuditEvent( repositoryId, null, AuditEvent.DELETE_REMOTE_REPO, auditInformation );

        Configuration configuration = getArchivaConfiguration().getConfiguration();

        RemoteRepositoryConfiguration remoteRepositoryConfiguration =
            configuration.getRemoteRepositoriesAsMap().get( repositoryId );
        if ( remoteRepositoryConfiguration == null )
        {
            throw new RepositoryAdminException(
                "remoteRepository with id " + repositoryId + " not exist cannot remove it" );
        }

        configuration.removeRemoteRepository( remoteRepositoryConfiguration );

        // TODO use ProxyConnectorAdmin interface ?
        // [MRM-520] Proxy Connectors are not deleted with the deletion of a Repository.
        List<ProxyConnectorConfiguration> proxyConnectors = new ArrayList<>( configuration.getProxyConnectors() );
        for ( ProxyConnectorConfiguration proxyConnector : proxyConnectors )
        {
            if ( StringUtils.equals( proxyConnector.getTargetRepoId(), repositoryId ) )
            {
                configuration.removeProxyConnector( proxyConnector );
            }
        }

        saveConfiguration( configuration );

        return Boolean.TRUE;
    }

    @Override
    public Boolean updateRemoteRepository( RemoteRepository remoteRepository, AuditInformation auditInformation )
        throws RepositoryAdminException
    {

        String repositoryId = remoteRepository.getId();

        triggerAuditEvent( repositoryId, null, AuditEvent.MODIFY_REMOTE_REPO, auditInformation );

        // update means : remove and add

        Configuration configuration = getArchivaConfiguration().getConfiguration();

        RemoteRepositoryConfiguration remoteRepositoryConfiguration =
            configuration.getRemoteRepositoriesAsMap().get( repositoryId );
        if ( remoteRepositoryConfiguration == null )
        {
            throw new RepositoryAdminException(
                "remoteRepository with id " + repositoryId + " not exist cannot remove it" );
        }

        configuration.removeRemoteRepository( remoteRepositoryConfiguration );

        remoteRepositoryConfiguration = getRemoteRepositoryConfiguration( remoteRepository );
        configuration.addRemoteRepository( remoteRepositoryConfiguration );
        saveConfiguration( configuration );

        return Boolean.TRUE;
    }

    @Override
    public Map<String, RemoteRepository> getRemoteRepositoriesAsMap()
        throws RepositoryAdminException
    {
        java.util.Map<String, RemoteRepository> map = new HashMap<>();

        for ( RemoteRepository repo : getRemoteRepositories() )
        {
            map.put( repo.getId(), repo );
        }

        return map;
    }

    @Override
    public IndexingContext createIndexContext( RemoteRepository remoteRepository )
        throws RepositoryAdminException
    {
        try
        {
            String appServerBase = getRegistry().getString( "appserver.base" );

            String contextKey = "remote-" + remoteRepository.getId();
            IndexingContext indexingContext = indexer.getIndexingContexts().get( contextKey );
            if ( indexingContext != null )
            {
                return indexingContext;
            }
            // create remote repository path
            File repoDir = new File( appServerBase, "data/remotes/" + remoteRepository.getId() );
            if ( !repoDir.exists() )
            {
                repoDir.mkdirs();
            }

            File indexDirectory = null;

            // is there configured indexDirectory ?
            String indexDirectoryPath = remoteRepository.getIndexDirectory();

            if ( StringUtils.isNotBlank( indexDirectoryPath ) )
            {
                if ( new File( indexDirectoryPath ).isAbsolute() )
                {
                    indexDirectory = new File( indexDirectoryPath );
                }
                else
                {
                    indexDirectory = new File( repoDir, indexDirectoryPath );
                }
            }
            // if not configured use a default value
            if ( indexDirectory == null )
            {
                indexDirectory = new File( repoDir, ".index" );
            }
            if ( !indexDirectory.exists() )
            {
                indexDirectory.mkdirs();
            }
            return indexer.addIndexingContext( contextKey, remoteRepository.getId(), repoDir, indexDirectory,
                                               remoteRepository.getUrl(), calculateIndexRemoteUrl( remoteRepository ),
                                               mavenIndexerUtils.getAllIndexCreators() );
        }
        catch ( MalformedURLException e )
        {
            throw new RepositoryAdminException( e.getMessage(), e );
        }
        catch ( IOException e )
        {
            throw new RepositoryAdminException( e.getMessage(), e );
        }
        catch ( UnsupportedExistingLuceneIndexException e )
        {
            throw new RepositoryAdminException( e.getMessage(), e );
        }

    }

    protected String calculateIndexRemoteUrl( RemoteRepository remoteRepository )
    {
        if ( StringUtils.startsWith( remoteRepository.getRemoteIndexUrl(), "http" ) )
        {
            String baseUrl = remoteRepository.getRemoteIndexUrl();
            return baseUrl.endsWith( "/" ) ? StringUtils.substringBeforeLast( baseUrl, "/" ) : baseUrl;
        }
        String baseUrl = StringUtils.endsWith( remoteRepository.getUrl(), "/" ) ? StringUtils.substringBeforeLast(
            remoteRepository.getUrl(), "/" ) : remoteRepository.getUrl();

        baseUrl = StringUtils.isEmpty( remoteRepository.getRemoteIndexUrl() )
            ? baseUrl + "/.index"
            : baseUrl + "/" + remoteRepository.getRemoteIndexUrl();
        return baseUrl;

    }

    private RemoteRepositoryConfiguration getRemoteRepositoryConfiguration( RemoteRepository remoteRepository )
    {
        RemoteRepositoryConfiguration remoteRepositoryConfiguration = new RemoteRepositoryConfiguration();
        remoteRepositoryConfiguration.setId( remoteRepository.getId() );
        remoteRepositoryConfiguration.setPassword( remoteRepository.getPassword() );
        remoteRepositoryConfiguration.setTimeout( remoteRepository.getTimeout() );
        remoteRepositoryConfiguration.setUrl( remoteRepository.getUrl() );
        remoteRepositoryConfiguration.setUsername( remoteRepository.getUserName() );
        remoteRepositoryConfiguration.setLayout( remoteRepository.getLayout() );
        remoteRepositoryConfiguration.setName( remoteRepository.getName() );
        remoteRepositoryConfiguration.setDownloadRemoteIndex( remoteRepository.isDownloadRemoteIndex() );
        remoteRepositoryConfiguration.setRemoteIndexUrl( remoteRepository.getRemoteIndexUrl() );
        remoteRepositoryConfiguration.setRefreshCronExpression( remoteRepository.getCronExpression() );
        remoteRepositoryConfiguration.setIndexDir( remoteRepository.getIndexDirectory() );
        remoteRepositoryConfiguration.setRemoteDownloadNetworkProxyId(
            remoteRepository.getRemoteDownloadNetworkProxyId() );
        remoteRepositoryConfiguration.setRemoteDownloadTimeout( remoteRepository.getRemoteDownloadTimeout() );
        remoteRepositoryConfiguration.setDownloadRemoteIndexOnStartup(
            remoteRepository.isDownloadRemoteIndexOnStartup() );
        remoteRepositoryConfiguration.setDescription( remoteRepository.getDescription() );
        remoteRepositoryConfiguration.setExtraHeaders( remoteRepository.getExtraHeaders() );
        remoteRepositoryConfiguration.setExtraParameters( remoteRepository.getExtraParameters() );
        remoteRepositoryConfiguration.setCheckPath(remoteRepository.getCheckPath());
        return remoteRepositoryConfiguration;
    }

}
