/** 
 * Copyright simpligility technologies inc. http://www.simpligility.com
 * Licensed under Eclipse Public License - v 1.0 http://www.eclipse.org/legal/epl-v10.html
 */
package com.simpligility.maven.provisioner;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.TreeSet;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.NotFileFilter;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.deployment.DeployRequest;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import org.slf4j.LoggerFactory;

import com.simpligility.maven.Gav;
import com.simpligility.maven.GavUtil;
import com.simpligility.maven.MavenConstants;

import org.slf4j.Logger;

public class MavenRepositoryDeployer
{
    private static Logger logger = LoggerFactory.getLogger( "MavenRepositoryHelper" );

    private File repositoryPath;

    private RepositorySystem system;

    private DefaultRepositorySystemSession session;

    private final TreeSet<String> successfulDeploys = new TreeSet<String>();

    private final TreeSet<String> failedDeploys = new TreeSet<String>();

    private final TreeSet<String> skippedDeploys = new TreeSet<String>();
    
    private final TreeSet<String> potentialDeploys = new TreeSet<String>();

    public MavenRepositoryDeployer( File repositoryPath )
    {
        this.repositoryPath = repositoryPath;
        initialize();
    }
    
    private void initialize()
    {
        system = RepositoryHandler.getRepositorySystem();
        session = RepositoryHandler.getRepositorySystemSession( system, repositoryPath );
    }
    
    public static Collection<File> getLeafDirectories( File repoPath ) 
    {
        // Using commons-io, if performance or so is a problem it might be worth looking at the Java 8 streams API
        // e.g. http://blog.jooq.org/2014/01/24/java-8-friday-goodies-the-new-new-io-apis/
        // not yet though..
       Collection<File> subDirectories =
            FileUtils.listFilesAndDirs( repoPath, DirectoryFileFilter.DIRECTORY,
                VisibleDirectoryFileFilter.DIRECTORY );
        Collection<File> leafDirectories = new ArrayList<File>();
        for ( File subDirectory : subDirectories )
        {
            if ( isLeafVersionDirectory( subDirectory ) && subDirectory != repoPath )
            {
                leafDirectories.add( subDirectory );
            }
        }
        return leafDirectories;
    }
    
    /**
     * Determine if it is a leaf directory with artifacts in it. Criteria used is that there is no subdirectory.
     * 
     * @param subDirectory
     * @return
     */
    private static boolean isLeafVersionDirectory( File subDirectory )
    {
        boolean isLeafVersionDirectory;
        Collection<File> subDirectories =
            FileUtils.listFilesAndDirs( subDirectory,
                (IOFileFilter) VisibleDirectoryFileFilter.DIRECTORY,
                (IOFileFilter) VisibleDirectoryFileFilter.DIRECTORY );
        // it finds at least itself so have to check for > 1
        isLeafVersionDirectory = subDirectories.size() > 1 ? false : true; 
        return isLeafVersionDirectory;
    }
    
    public static Collection<File> getPomFiles( File repoPath )
    {
        Collection<File> pomFiles = new ArrayList<File>();
        Collection<File> leafDirectories = getLeafDirectories( repoPath );
        for ( File leafDirectory : leafDirectories )
        {
            IOFileFilter fileFilter = new AndFileFilter( new WildcardFileFilter( "*.pom" ),
                                               new NotFileFilter( new SuffixFileFilter( "sha1" ) ) );
            pomFiles.addAll( FileUtils.listFiles( leafDirectory, fileFilter, null ) );
        }
        return pomFiles;
    }


    public void deployToRemote( String targetUrl, String username, String password, Boolean checkTarget,
        Boolean verifyOnly )
    {
        Collection<File> leafDirectories = getLeafDirectories( repositoryPath );

        for ( File leafDirectory : leafDirectories )
        {
            String leafAbsolutePath = leafDirectory.getAbsoluteFile().toString();
            int repoAbsolutePathLength = repositoryPath.getAbsoluteFile().toString().length();
            String leafRepoPath = leafAbsolutePath.substring( repoAbsolutePathLength + 1, leafAbsolutePath.length() );

            Gav gav = GavUtil.getGavFromRepositoryPath( leafRepoPath );

            boolean pomInTarget = false;
            if ( checkTarget ) 
            {
                pomInTarget = checkIfPomInTarget( targetUrl, username, password, gav );
            }
            
            if ( pomInTarget ) 
            {
                logger.info( "Found POM for " + gav + " already in target. Skipping deployment." );
                skippedDeploys.add( gav.toString() );
            } 
            else
            {
                // only interested in files using the artifactId-version* pattern
                // don't bother with .sha1 files
                IOFileFilter fileFilter =
                    new AndFileFilter( new WildcardFileFilter( gav.getArtifactId() + "-" + gav.getVersion() + "*" ),
                                       new NotFileFilter( new SuffixFileFilter( "sha1" ) ) );
                Collection<File> artifacts = FileUtils.listFiles( leafDirectory, fileFilter, null );

                Authentication auth = new AuthenticationBuilder().addUsername( username ).addPassword( password )
                                .build();

                RemoteRepository distRepo = new RemoteRepository.Builder( "repositoryIdentifier", "default", targetUrl )
                        .setProxy( ProxyHelper.getProxy( targetUrl ) )
                        .setAuthentication( auth ).build();

                DeployRequest deployRequest = new DeployRequest();
                deployRequest.setRepository( distRepo );
                for ( File file : artifacts )
                {
                    String extension;
                    if ( file.getName().endsWith( "tar.gz" ) )
                    {
                        extension = "tar.gz";
                    }
                    else
                    {
                        extension = FilenameUtils.getExtension( file.getName() );
                    }

                    String baseFileName = gav.getFilenameStart() + "." + extension;
                    String fileName = file.getName();
                    String g = gav.getGroupId();
                    String a = gav.getArtifactId();
                    String v = gav.getVersion();
                    
                    Artifact artifact = null;
                    if ( gav.getPomFilename().equals( fileName ) )
                    {
                        artifact = new DefaultArtifact( g, a, MavenConstants.POM, v );
                    }
                    else if ( gav.getJarFilename().equals( fileName ) )
                    {
                        artifact = new DefaultArtifact( g, a, MavenConstants.JAR, v );
                    }
                    else if ( gav.getSourceFilename().equals( fileName ) )
                    {
                        artifact = new DefaultArtifact( g, a, MavenConstants.SOURCES, MavenConstants.JAR, v );
                    }
                    else if ( gav.getJavadocFilename().equals( fileName ) )
                    {
                        artifact = new DefaultArtifact( g, a, MavenConstants.JAVADOC, MavenConstants.JAR, v );
                    }
                    else if ( baseFileName.equals( fileName ) )
                    {
                        artifact = new DefaultArtifact( g, a, extension, v );
                    }
                    else
                    {
                        String classifier =
                            file.getName().substring( gav.getFilenameStart().length() + 1,
                                                      file.getName().length() - ( "." + extension ).length() );
                        artifact = new DefaultArtifact( g, a, classifier, extension, v );
                    }

                    if ( artifact != null )
                    {
                        artifact = artifact.setFile( file );
                        deployRequest.addArtifact( artifact );
                    }
                }

                try
                {
                    if ( verifyOnly )
                    {
                      for ( Artifact artifact : deployRequest.getArtifacts() )
                      {
                          potentialDeploys.add( artifact.toString() );
                      }
                    }
                    else
                    {
                      system.deploy( session, deployRequest );
                      for ( Artifact artifact : deployRequest.getArtifacts() )
                      {
                          successfulDeploys.add( artifact.toString() );
                      }
                    }
                }
                catch ( Exception e )
                {
                    logger.info( "Deployment failed with " + e.getMessage() + ", artifact might be deployed already." );
                    for ( Artifact artifact : deployRequest.getArtifacts() ) 
                    {
                        failedDeploys.add( artifact.toString() );
                    }
                }
            }
        }
    }

    /**
     * Check if POM file for provided gav can be found in target. Just does
     * a HTTP get of the header and verifies http status OK 200.
     * @param targetUrl url of the target repository
     * @param gav group artifact version string
     * @return {@code true} if the pom.xml already exists in the target repository
     */
    private boolean checkIfPomInTarget( String targetUrl, String username, String password, Gav gav )
    {
        boolean alreadyInTarget = false;
        
        String artifactUrl = targetUrl + gav.getRepositoryURLPath() + gav.getPomFilename();
        logger.debug( "Headers for {}", artifactUrl );

        HttpHead httphead = new HttpHead( artifactUrl );

        if ( !StringUtils.isEmpty( username ) && ! StringUtils.isEmpty( password ) )
        {
          String encoding = java.util.Base64.getEncoder().encodeToString( ( username + ":" + password ).getBytes() );
          httphead.setHeader( "Authorization", "Basic " + encoding );
        }

        try ( CloseableHttpClient httpClient = HttpClientBuilder.create().build() )
        {
          HttpResponse response = httpClient.execute( httphead );
          int statusCode = response.getStatusLine().getStatusCode();
          if ( statusCode == HttpURLConnection.HTTP_OK )
          {
              alreadyInTarget = true;
          }
          else
          {
              logger.debug( "Headers not found HTTP: {}", statusCode );
          }
        } 
        catch ( IOException ioe )
        {
          logger.warn( "Could not check target repository for already existing pom.xml.", ioe );
        }
        return alreadyInTarget;
    }


    public String listSucessfulDeployments()
    {
        StringBuilder builder = new StringBuilder();
        builder.append( "Sucessful Deployments:\n\n" );
        for ( String artifact : successfulDeploys )
        {
            builder.append( artifact + "\n" );
        }
        return builder.toString();
    }

    public String listFailedDeployments()
    {
        StringBuilder builder = new StringBuilder();
        builder.append( "Failed Deployments:\n\n" );
        for ( String artifact : failedDeploys )
        {
            builder.append( artifact + "\n" );
        }

        return builder.toString();
    }
    
    public String listSkippedDeployment()
    {
        StringBuilder builder = new StringBuilder();
        builder.append( "Skipped Deployments (POM already in target):\n\n" );
        for ( String artifact : skippedDeploys )
        {
            builder.append( artifact + "\n" );
        }

        return builder.toString();
    }

    public String listPotentialDeployment()
    {
        StringBuilder builder = new StringBuilder();
        builder.append( "Potential Deployments :\n\n" );
        for ( String artifact : potentialDeploys )
        {
            builder.append( artifact + "\n" );
        }

        return builder.toString();
    }

    public static Gav getCoordinates ( File pomFile ) throws Exception
    {
        BufferedReader in = new BufferedReader( new FileReader( pomFile ) );
        MavenXpp3Reader reader = new MavenXpp3Reader();
        Model model = reader.read( in );
        // get coordinates and take care of inheritance and default
        String g = model.getGroupId();
        if ( StringUtils.isEmpty( g ) ) 
        {
            g = model.getParent().getGroupId();
        }
        String a = model.getArtifactId();
        if ( StringUtils.isEmpty( a ) ) 
        {
            a = model.getParent().getArtifactId();
        }
        String v = model.getVersion();
        if ( StringUtils.isEmpty( v ) ) 
        {
            v = model.getParent().getVersion();
        }
        String p = model.getPackaging();
        if ( StringUtils.isEmpty( p ) ) 
        {
            p = MavenConstants.JAR;
        }
        Gav gav = new Gav( g, a, v, p );
        return gav;
    }

    public boolean hasFailure() 
    {
      return failedDeploys.size() > 0;
    }

    public String getFailureMessage() 
    {
      return "Failed to deploy some artifacts.";
    }
}