package ru.bitel.bgbilling.modules.inet.dyn.device.cisco.ipdhcp;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;

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

import bitel.billing.server.radius.RadiusStandartAttributes;
import ru.bitel.bgbilling.kernel.container.managed.ServerContext;
import ru.bitel.bgbilling.kernel.event.EventProcessor;
import ru.bitel.bgbilling.kernel.network.radius.RadiusAttribute.RadiusAttributeInteger;
import ru.bitel.bgbilling.kernel.network.radius.RadiusDictionary;
import ru.bitel.bgbilling.kernel.network.radius.RadiusListenerWorker;
import ru.bitel.bgbilling.kernel.network.radius.RadiusPacket;
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.enums.InetConnectionStatus;
import ru.bitel.bgbilling.modules.inet.common.bean.enums.InetServState;
import ru.bitel.bgbilling.modules.inet.common.event.sa.InetSaConnectionCloseEvent;
import ru.bitel.bgbilling.modules.inet.dyn.device.cisco.CiscoUtils;
import ru.bitel.bgbilling.modules.inet.dyn.device.cisco.ISGProtocolHandler;
import ru.bitel.bgbilling.modules.inet.server.radius.DiscardedAccountingRequestHandler;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Utils;
import ru.bitel.common.inet.IpRange;

/**
 * Обработчик, закрывающий соединения, для которых не найден абонент.
 */
public class ISGProtocolHandlerIpDhcp
	extends ISGProtocolHandler
	implements DiscardedAccountingRequestHandler
{
	private static final Logger logger = LogManager.getLogger();

	private int moduleId;
	private int deviceId;

	private int contractId;
	private int servId;

	private List<IpRange> excludeRangeList;

	private static final Pattern patternV4 = Pattern.compile( "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" );

	private static final Pattern patternV6 = Pattern.compile( "^([\\dA-F]{1,4}:|((?=.*(::))(?!.*\\3.+\\3))\\3?)([\\dA-F]{1,4}(\\3|:\\b)|\\2){5}"
															  + "(([\\dA-F]{1,4}(\\3|:\\b|$)|\\2){2}|(((2[0-4]|1\\d|[1-9])?\\d|25[0-5])\\.?\\b){4})\\z" );

	@Override
	public void init( Setup setup, int moduleId, InetDevice inetDevice, InetDeviceType inetDeviceType, ParameterMap deviceConfig )
		throws Exception
	{
		super.init( setup, moduleId, inetDevice, inetDeviceType, deviceConfig );

		this.moduleId = moduleId;
		this.deviceId = inetDevice.getId();

		this.contractId = deviceConfig.getInt( "radius.connection.close.unknown.contractId", 0 );
		this.servId = deviceConfig.getInt( "radius.connection.close.unknown.servId", deviceConfig.getInt( "radius.disable.servId", 0 ) );

		final List<IpRange> excludeRangeList = new ArrayList<IpRange>();

		for ( String range : deviceConfig.get( "radius.connection.close.unknown.excludeRanges", "" ).split( "\\s*;\\s*" ) )
		{
			if ( Utils.isBlankString( range ) )
			{
				continue;
			}
			
			String[] rangeValue = range.split( "\\s*-\\s*" );
			if ( rangeValue.length != 2 )
			{
				logger.error( "Can't parse " + range );
				continue;
			}

			try
			{
				InetAddress addressFrom = InetAddress.getByName( rangeValue[0] );
				InetAddress addressTo = InetAddress.getByName( rangeValue[1] );

				excludeRangeList.add( new IpRange( addressFrom.getAddress(), addressTo.getAddress() ) );
			}
			catch( UnknownHostException e )
			{
				logger.error( e.getMessage(), e );
			}

		}

		logger.info( "radius.connection.close.unknown.excludeRanges: " + excludeRangeList );

		this.excludeRangeList = excludeRangeList.size() > 0 ? excludeRangeList : Collections.<IpRange> emptyList();
	}

	@Override
	public void discard( ServerContext context, RadiusListenerWorker<?> req, RadiusPacket request, RadiusPacket response )
		throws Exception
	{
		final int statusType = request.getIntAttribute( -1, RadiusDictionary.Acct_Status_Type, 0 );
		// stop-пакеты не обрабатываем
		if ( statusType == 2 )
		{
			return;
		}

		final String parentAcctSessionId = CiscoUtils.getStringAttribute( request, radiusVendor, parentAcctSessionIdType, parentAcctSessionIdPrefix, null );
		// дочерние (сервисные) сессии не обрабатываем
		if ( Utils.notEmptyString( parentAcctSessionId ) )
		{
			return;
		}

		final String username = request.getStringAttribute( -1, RadiusDictionary.User_Name, null );
		if ( !patternV4.matcher( username ).matches() && !patternV6.matcher( username ).matches() )
		{
			logger.info( "Not found inetServ for connection. Username is not an IP-address" );
			return;
		}

		try
		{
			final InetAddress inetAddress = InetAddress.getByName( username );
			final byte[] address = inetAddress.getAddress();

			// проверяем, что адрес не в исключениях
			for ( IpRange range : excludeRangeList )
			{
				if ( IpRange.inRange( address, address, range.getAddressFrom(), range.getAddressTo() ) )
				{
					return;
				}
			}

			if ( contractId <= 0 )
			{
				logger.info( "Not found inetServ for connection (disconnection for unknown disabled)" );
				return;
			}
			
			logger.info( "Sending event to close unknown connection" );

			final String acctSessionId = request.getStringAttribute( -1, RadiusDictionary.Acct_Session_Id, null );
			final RadiusAttributeInteger nasPortAttribute = request.getAttribute( -1, RadiusStandartAttributes.NAS_Port );
			final String callingStationId = request.getStringAttribute( -1, RadiusDictionary.Calling_Station_Id, "" );
			final String calledStationId = request.getStringAttribute( -1, RadiusDictionary.Called_Station_Id, "" );
			final int nasPort = nasPortAttribute != null ? nasPortAttribute.getValue() : 0;

			InetConnection connection = new InetConnection();
			connection.setAcctSessionId( acctSessionId );
			connection.setCalledStationId( calledStationId );
			connection.setCallingStationId( callingStationId );
			connection.setDeviceId( deviceId );
			connection.setDevicePort( nasPort );

			connection.setConnectionStart( new Date() );

			connection.setContractId( contractId );
			connection.setServId( servId );
			connection.setUsername( username );

			connection.setInetAddressBytes( address );
			connection.setIpResourceId( 0 );

			connection.setConnectionStatus( InetConnectionStatus.STATUS_ALIVE );

			connection.setDeviceState( InetServState.STATE_ENABLE.getCode() );

			connection.setDeviceOptions( new HashSet<Integer>() );

			EventProcessor.getInstance().publish( new InetSaConnectionCloseEvent( moduleId, 0, connection ) );
		}
		catch( UnknownHostException ex )
		{
			logger.warn( ex.getMessage() );
		}
	}
}
