package ru.bitel.bgbilling.modules.inet.dyn.device.terminal;

import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import ru.bitel.bgbilling.apps.inet.access.sa.ServiceActivator;
import ru.bitel.bgbilling.apps.inet.access.sa.ServiceActivatorEvent;
import ru.bitel.bgbilling.kernel.admin.errorlog.common.bean.AlarmErrorMessage;
import ru.bitel.bgbilling.kernel.admin.errorlog.server.AlarmSender;
import ru.bitel.bgbilling.modules.inet.common.bean.InetConnection;
import ru.bitel.bgbilling.modules.inet.common.bean.InetDevice;
import ru.bitel.bgbilling.modules.inet.common.bean.InetDeviceType;
import ru.bitel.bgbilling.modules.inet.common.bean.InetServ;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.bgbilling.server.util.ssh.SSHSession;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Utils;

/**
 *  {@inheritDoc}
 *  @see AbstractTerminalServiceActivator
 */
public class SSHServiceActivator
    extends AbstractTerminalServiceActivator
    implements ServiceActivator
{
	private static final Logger logger = LogManager.getLogger();

	/**
	 *  Regexp как признак выхода
	 */
	protected String regexp;

	protected String endSequence;

	protected SSHSession session;

	protected boolean lazyConnect;
	
	//путь к файлу id_rsa без ".pub"
    private String privateKeyFile = null;
	
	@Override
	public Object init( Setup setup, int moduleId, InetDevice device, InetDeviceType deviceType, ParameterMap config )
	    throws Exception
	{
		super.init( setup, moduleId, device, deviceType, config );
		
		if( this.port <= 0 )
		{
			this.port = 22;
		}

		this.endSequence = config.get( "sa.endSequence", null );
		this.regexp = config.get( "sa.exitRegexp", null );

		this.lazyConnect = config.getInt( "sa.lazyConnect", 0 ) > 0;
		this.privateKeyFile = config.get( "sa.ssh.privateKeyFile", null );

		return null;
	}

	@Override
	public Object destroy()
	    throws Exception
	{
		return super.destroy();
	}

	@Override
	public Object connect()
		throws Exception
	{
		if( lazyConnect )
		{
			return null;
		}

		return connectImpl();
	}

	protected Object connectImpl()
		throws Exception
	{
		try
		{
			if( connectionSemaphore != null )
			{
				connectionSemaphoreAcquired = connectionSemaphore.tryAcquire( 3, TimeUnit.MINUTES );
			}
			
			SSHSession session = new SSHSession( host, port, username, password );
			session.setTimeout( timeout );
			session.setReadWait( readWait );
			session.setPrivateKeyFile( this.privateKeyFile );

			if( Utils.notBlankString( endSequence ) )
			{
				session.setEndString( endSequence );
			}

			if( Utils.notBlankString( regexp ) )
			{
				session.setRegexp( regexp );
			}

			session.connect();
			logger.info( "Connected to " + device );

			this.session = session;
		}
		catch( Exception ex )
		{
			logger.error( "Can't connect to " + device + " to " + host + ":" + port + ". Check host/port, username/password and sa.endSequence (command prompt)" );
			throw ex;
		}

		return super.connect();
	}
	
	protected SSHSession getSession()
		throws Exception
	{
		if( session != null )
		{
			return session;
		}

		connectImpl();

		return session;
	}

	@Override
	public Object disconnect()
		throws Exception
	{
		try
		{
			if( session != null )
			{
				try
				{
					super.disconnect();

					logger.info( "[" + device + "] executeAsync: " + this.exitCommand );
					session.doCommandAsync( this.exitCommand );
				}
				finally
				{
					session.disconnect();

					session = null;

					logger.debug( "Disconnected" );
				}
			}
			else
			{
				logger.debug( "Not connected - skip disconnection" );
			}
		}
		finally
		{
			if( connectionSemaphore != null && connectionSemaphoreAcquired )
			{
				connectionSemaphore.release();
			}
		}

		return null;
	}
	
	private static Pattern returnCodePattern = Pattern.compile( "RETURN_CODE=(\\d+)" );

	@Override
	protected void executeCommand( String command )
		throws Exception
	{
		final SSHSession session = getSession();
		
		logger.info( "[" + device + "] execute: " + command );

		String result;
		try
		{
			result = session.doCommand( command );
		}
		catch( TimeoutException ex )
		{
			logger.error( "Timeout waiting command prompt (endSequence) when executing command." );
			throw ex;
		}

		logger.info( result );

		if( Utils.notBlankString( result ) )
		{
			Matcher m = returnCodePattern.matcher( result );
			while( m.find() )
			{
				String returnCode = m.group( 1 );

				if( Utils.parseInt( returnCode, 0 ) == 0 )
				{
					continue;
				}

				logger.error( "Command \"" + command + "\" executed with RETURN_CODE=" + returnCode );

				String key = "inet.sa.ssh.returnCode";
				long time = System.currentTimeMillis();

				if( AlarmSender.needAlarmSend( key, time, 600000 ) )
				{
					String subject = "Inet: Ошибка работы обработчика активации сервисов [" + deviceId + "] " + device.getGuiTitle();
					String message = "Ошибка работы обработчика активации сервисов [" + deviceId + "] " + device.getGuiTitle()
									 + "\n" + device.getHost()
									 + "\n(" + device.getComment() + ")\n\n"
									 + "Исполнение команды \"" + command + "\" завершилось с ошибкой " + returnCode + "!";

					AlarmErrorMessage alarm = new AlarmErrorMessage( key, subject, message );

					AlarmSender.sendAlarm( alarm, time );
				}
			}
		}
	}

	@Override
	protected Object getValue( ServiceActivatorEvent e, InetServ serv, InetConnection connection, Set<Integer> options, String macros, Object[] args, Object[] globalArgs )
	    throws Exception
	{
		if( "setEndSequence".equals( macros ) )
		{
			if( args.length > 0 )
			{
				String endSequence = (String)args[0];
				if( Utils.notEmptyString( endSequence ) )
				{
					getSession().setEndString( (String)args[0] );
					return "";
				}
			}

			getSession().setEndString( this.endSequence );
			return "";
		}
		else
		{
			return super.getValue( e, serv, connection, options, macros, args, globalArgs );
		}
	}
}
