package io.subutai.core.systemmanager.impl;


import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import javax.annotation.security.RolesAllowed;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.io.comparator.LastModifiedFileComparator;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;

import io.subutai.common.command.CommandException;
import io.subutai.common.command.CommandResult;
import io.subutai.common.command.RequestBuilder;
import io.subutai.common.dao.DaoManager;
import io.subutai.common.exception.ActionFailedException;
import io.subutai.common.peer.HostNotFoundException;
import io.subutai.common.peer.PeerInfo;
import io.subutai.common.peer.ResourceHost;
import io.subutai.common.peer.ResourceHostException;
import io.subutai.common.settings.Common;
import io.subutai.common.settings.SubutaiInfo;
import io.subutai.common.util.ServiceLocator;
import io.subutai.core.environment.api.EnvironmentManager;
import io.subutai.core.bazaarmanager.api.BazaarManager;
import io.subutai.core.identity.api.IdentityManager;
import io.subutai.core.identity.api.model.User;
import io.subutai.core.peer.api.PeerManager;
import io.subutai.core.systemmanager.api.SystemManager;
import io.subutai.core.systemmanager.api.pojo.AdvancedSettings;
import io.subutai.core.systemmanager.api.pojo.NetworkSettings;
import io.subutai.core.systemmanager.api.pojo.PeerSettings;
import io.subutai.core.systemmanager.api.pojo.SystemInfo;
import io.subutai.core.systemmanager.api.pojo.UpdateDto;
import io.subutai.core.systemmanager.impl.dao.UpdateDao;
import io.subutai.core.systemmanager.impl.entity.UpdateEntity;
import io.subutai.core.systemmanager.impl.pojo.AdvancedSettingsPojo;
import io.subutai.core.systemmanager.impl.pojo.NetworkSettingsPojo;
import io.subutai.core.systemmanager.impl.pojo.PeerSettingsPojo;
import io.subutai.core.systemmanager.impl.pojo.SystemInfoPojo;


public class SystemManagerImpl implements SystemManager
{
    private static final Logger LOG = LoggerFactory.getLogger( SystemManagerImpl.class );
    private static final String UPDATE_IN_PROGRESS_MSG = "Update is in progress";

    private IdentityManager identityManager;
    private PeerManager peerManager;
    private DaoManager daoManager;
    private UpdateDao updateDao;


    private volatile boolean isUpdateInProgress = false;


    @Override
    @RolesAllowed( "System-Management|Read" )
    public SystemInfo getSystemInfo()
    {
        SystemInfoPojo pojo = new SystemInfoPojo();

        pojo.setGitCommitId( SubutaiInfo.getCommitId() );
        pojo.setGitBranch( SubutaiInfo.getBranch() );
        pojo.setGitCommitUserName( SubutaiInfo.getCommitterUserName() );
        pojo.setGitCommitUserEmail( SubutaiInfo.getCommitterUserEmail() );
        pojo.setGitBuildUserName( SubutaiInfo.getBuilderUserName() );
        pojo.setGitBuildUserEmail( SubutaiInfo.getBuilderUserEmail() );
        pojo.setGitBuildTime( SubutaiInfo.getBuildTime() );
        pojo.setProjectVersion( SubutaiInfo.getVersion() );

        try
        {
            ResourceHost host = peerManager.getLocalPeer().getManagementHost();
            pojo.setRhVersion( host.getRhVersion().replace( "Subutai version", "" ).trim() );
            pojo.setP2pVersion( host.getP2pVersion().replace( "p2p Cloud project", "" ).trim() );
            pojo.setOsName( host.getOsName().trim() );
        }
        catch ( HostNotFoundException | ResourceHostException e )
        {
            LOG.error( "Error getting system info: {}", e.getMessage() );

            if ( StringUtils.isBlank( pojo.getRhVersion() ) )
            {
                pojo.setRhVersion( "Failed to obtain version" );
            }

            if ( StringUtils.isBlank( pojo.getP2pVersion() ) )
            {
                pojo.setP2pVersion( "Failed to obtain version" );
            }

            return pojo;
        }

        return pojo;
    }


    @Override
    @RolesAllowed( "System-Management|Update" )
    public void setPeerSettings()
    {
        identityManager.setPeerOwner( identityManager.getActiveUser() );
    }


    @Override
    @RolesAllowed( "System-Management|Read" )
    public PeerSettings getPeerSettings()
    {
        String peerOwnerId = identityManager.getPeerOwnerId();
        User user = identityManager.getUserByKeyId( peerOwnerId );

        PeerSettings pojo = new PeerSettingsPojo();

        pojo.setPeerOwnerId( peerOwnerId );
        pojo.setUserPeerOwnerName( user.getUserName() );

        return pojo;
    }


    @Override
    @RolesAllowed( "System-Management|Read" )
    public NetworkSettings getNetworkSettings() throws ConfigurationException
    {
        NetworkSettingsPojo pojo = new NetworkSettingsPojo();

        PeerInfo localPeerInfo = peerManager.getLocalPeer().getPeerInfo();
        pojo.setPublicUrl( localPeerInfo.getPublicUrl() );
        pojo.setPublicSecurePort( localPeerInfo.getPublicSecurePort() );
        pojo.setUseRhIp( !localPeerInfo.isManualSetting() );
        pojo.setStartRange( Integer.parseInt( Common.P2P_PORT_RANGE_START ) );
        pojo.setEndRange( Integer.parseInt( Common.P2P_PORT_RANGE_END ) );
        pojo.setBazaarIp( Common.BAZAAR_IP );

        return pojo;
    }


    @Override
    @RolesAllowed( "System-Management|Update" )
    public void setNetworkSettings( final String publicUrl, final String publicSecurePort, final boolean useRhIp )
            throws ConfigurationException
    {
        try
        {
            peerManager
                    .setPublicUrl( peerManager.getLocalPeer().getId(), publicUrl, Integer.parseInt( publicSecurePort ),
                            useRhIp );
        }
        catch ( Exception e )
        {
            throw new ConfigurationException( e );
        }
    }


    @Override
    @RolesAllowed( "System-Management|Read" )
    public AdvancedSettings getAdvancedSettings( String logFile )
    {
        AdvancedSettingsPojo pojo = new AdvancedSettingsPojo();

        String content;
        try
        {
            Path karafLogDirPath = Paths.get( System.getenv( "SUBUTAI_APP_DATA_PATH" ), "/data/log/" );

            Path currentKarafLogFilePath;
            if ( StringUtils.isBlank( logFile ) )
            {
                currentKarafLogFilePath = karafLogDirPath.resolve( "karaf.log" );
            }
            else
            {
                currentKarafLogFilePath = karafLogDirPath.resolve( logFile );
            }

            content = new String( Files.readAllBytes( currentKarafLogFilePath ) );

            pojo.setKarafLogs( content );

            File[] karafLogFiles = karafLogDirPath.toFile().listFiles( new FileFilter()
            {
                @Override
                public boolean accept( final File pathname )
                {
                    return pathname.isFile() && pathname.getName().startsWith( "karaf.log" );
                }
            } );

            assert karafLogFiles != null;

            Arrays.sort( karafLogFiles, LastModifiedFileComparator.LASTMODIFIED_REVERSE );

            List<String> karafLogFileNames = Lists.newArrayList();

            for ( File karafLogFile : karafLogFiles )
            {
                karafLogFileNames.add( karafLogFile.getName() );
            }

            pojo.setKarafLogFiles( karafLogFileNames );
        }
        catch ( IOException e )
        {
            LOG.warn( e.getMessage() );
        }

        return pojo;
    }


    @Override
    @RolesAllowed( "System-Management|Read" )
    public SystemInfo getManagementUpdates()
    {
        SystemInfoPojo info = ( SystemInfoPojo ) getSystemInfo();

        try
        {
            ResourceHost host = peerManager.getLocalPeer().getManagementHost();

            CommandResult result = host.execute(
                    new RequestBuilder( "subutai update management -c ; subutai update rh -c" ).withTimeout(
                            ( int ) TimeUnit.MINUTES.toSeconds(
                                    Common.MH_UPDATE_CHECK_TIMEOUT_MIN + Common.RH_UPDATE_CHECK_TIMEOUT_MIN ) ) );

            if ( result.getStdOut().contains( "Update is available" ) )
            {
                info.setUpdatesAvailable( true );
            }
            else
            {
                info.setUpdatesAvailable( false );
            }
        }
        catch ( HostNotFoundException e )
        {
            LOG.warn( e.getMessage() );

            info.setRhVersion( "No RH connected" );

            return info;
        }
        catch ( CommandException e )
        {
            LOG.warn( e.getMessage() );
        }

        return info;
    }


    @Override
    @RolesAllowed( "System-Management|Update" )
    public boolean updateManagement()
    {

        if ( isUpdateInProgress || isEnvironmentWorkflowInProgress() )
        {
            return false;
        }

        notifyBazaarThatPeerIsOffline();

        isUpdateInProgress = true;

        try
        {
            ResourceHost host = peerManager.getLocalPeer().getManagementHost();

            UpdateEntity updateEntity =
                    new UpdateEntity( SubutaiInfo.getVersion(), SubutaiInfo.getCommitId(), SubutaiInfo.getBuildTime() );

            updateDao.persist( updateEntity );

            boolean rhUpdated = updateRH();

            CommandResult result = host.execute( new RequestBuilder( "subutai update management" )
                    .withTimeout( ( int ) TimeUnit.MINUTES.toSeconds( Common.MH_UPDATE_TIMEOUT_MIN ) ) );

            boolean mhUpdated = !result.getStdOut().contains( "No update is available" ) && result.hasSucceeded();

            if ( mhUpdated || rhUpdated )
            {
                updateEntity.setCurrentVersion( "No change" );

                updateEntity.setCurrentCommitId( "System components updated" );

                updateDao.update( updateEntity );
            }
            else if ( !result.hasTimedOut() )
            {
                updateDao.remove( updateEntity.getId() );
            }

            return mhUpdated;
        }
        catch ( Exception e )
        {
            LOG.error( "Error updating Management: {}", e.getMessage() );

            throw new ActionFailedException( "Error updating Management: " + e.getMessage() );
        }
        finally
        {
            isUpdateInProgress = false;
        }
    }


    @Override
    public boolean isUpdateInProgress()
    {
        return isUpdateInProgress;
    }


    @Override
    public boolean isEnvironmentWorkflowInProgress()
    {
        EnvironmentManager environmentManager = ServiceLocator.lookup( EnvironmentManager.class );

        return !environmentManager.getActiveWorkflows().isEmpty() || !peerManager.getLocalPeer().getTasks().isEmpty();
    }


    public void notifyBazaarThatPeerIsOffline()
    {
        BazaarManager bazaarManager = ServiceLocator.lookup( BazaarManager.class );
        bazaarManager.notifyBazaarThatPeerIsOffline();
    }


    @Override
    public String getBazaarIp()
    {
        return Common.BAZAAR_IP;
    }


    public void init()
    {
        this.updateDao = new UpdateDao( daoManager.getEntityManagerFactory() );

        UpdateEntity updateEntity = updateDao.getLast();

        if ( updateEntity != null && updateEntity.getCurrentVersion() == null )
        {
            if ( Objects.equals( updateEntity.getPrevCommitId(), SubutaiInfo.getCommitId() ) )
            {
                updateEntity.setCurrentVersion( "No change" );

                if ( Objects.equals( updateEntity.getBuildTime(), SubutaiInfo.getBuildTime() ) )
                {
                    updateEntity.setCurrentCommitId( "Probably update was interrupted" );
                }
                else
                {
                    updateEntity.setCurrentCommitId( "Console was rebuilt" );
                }
            }
            else
            {
                updateEntity.setCurrentVersion( SubutaiInfo.getVersion() );

                updateEntity.setCurrentCommitId( SubutaiInfo.getCommitId() );
            }
            updateDao.update( updateEntity );
        }
    }


    @Override
    @RolesAllowed( "System-Management|Read" )
    public List<UpdateDto> getUpdates()
    {
        List<UpdateDto> updateDtos = new ArrayList<>();

        List<UpdateEntity> updateEntities = updateDao.getLast( 20 );

        for ( UpdateEntity updateEntity : updateEntities )
        {
            updateDtos.add( new UpdateDto( updateEntity.getUpdateDate(), updateEntity.getPrevVersion(),
                    updateEntity.getCurrentVersion() == null ? UPDATE_IN_PROGRESS_MSG :
                    updateEntity.getCurrentVersion(), updateEntity.getPrevCommitId(),
                    updateEntity.getCurrentCommitId() == null ? UPDATE_IN_PROGRESS_MSG :
                    updateEntity.getCurrentCommitId() ) );
        }


        return updateDtos;
    }


    public void setDaoManager( final DaoManager daoManager )
    {
        this.daoManager = daoManager;
    }


    public void setIdentityManager( final IdentityManager identityManager )
    {
        this.identityManager = identityManager;
    }


    public void setPeerManager( final PeerManager peerManager )
    {
        this.peerManager = peerManager;
    }


    private boolean updateRH()
    {
        try
        {
            return peerManager.getLocalPeer().getManagementHost().update();
        }
        catch ( HostNotFoundException e )
        {
            LOG.error( "Error updating MH: {}", e.getMessage() );
        }

        return false;
    }
}