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

import java.util.List;
import java.util.Map;
import java.util.Set;

import jakarta.annotation.Resource;
import javax.naming.NameNotFoundException;

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

import ru.bitel.bgbilling.apps.inet.access.Access;
import ru.bitel.bgbilling.apps.inet.access.InetConnectionRuntime;
import ru.bitel.bgbilling.kernel.container.managed.ServerContext;
import ru.bitel.bgbilling.kernel.container.resource.ResourceManager;
import ru.bitel.bgbilling.kernel.network.dhcp.DhcpPacket;
import ru.bitel.bgbilling.kernel.network.dhcp.DhcpProtocolHandler;
import ru.bitel.bgbilling.kernel.network.radius.RadiusAttribute;
import ru.bitel.bgbilling.kernel.network.radius.RadiusAttributeSet;
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.kernel.network.radius.RadiusProtocolHandler;
import ru.bitel.bgbilling.kernel.network.radius.RadiusSession;
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.modules.inet.common.bean.InetServType;
import ru.bitel.bgbilling.modules.inet.server.radius.InetNas;
import ru.bitel.bgbilling.modules.inet.server.radius.InetRadiusProcessor;
import ru.bitel.bgbilling.modules.inet.server.radius.InetRadiusSessionParams;
import ru.bitel.bgbilling.modules.inet.server.radius.RadiusAccessRequestHandler;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Utils;
import ru.bitel.common.inet.IpNet;
import ru.bitel.common.sql.ConnectionSet;

public class SmartEdgeDot1qProtocolHandler
	extends SmartEdgeProtocolHandler
	implements RadiusProtocolHandler, DhcpProtocolHandler, RadiusAccessRequestHandler
{
	private static final Logger logger = LogManager.getLogger();

	@Resource(name = "access")
	private Access access;

	// private int deviceId;

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

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

		ServerContext ctx = new ServerContext( setup, moduleId, 0 );
		ctx.init();
		try
		{
			ResourceManager rm = new ResourceManager();
			rm.inject( ctx, this, moduleId );

			ctx.commit();
		}
		catch( NameNotFoundException ex )
		{}
		catch( Exception ex )
		{
			logger.error( ex.getMessage(), ex );
		}
		finally
		{
			ctx.destroy();
		}
	}

	/**
	 * Обработка username, вытаскиваем agentDeviceId и VLAN
	 * @param request
	 */
	protected void processUsername( final RadiusPacket request )
	{
		String username = request.getStringAttribute( -1, RadiusDictionary.User_Name, null );

		int dotIndex1 = username.lastIndexOf( "." );
		int dotIndex2 = username.lastIndexOf( ".", dotIndex1 - 1 );

		int vlan = Utils.parseInt( username.substring( dotIndex1 + 1 ) );

		String agentDeviceId = username.substring( 0, dotIndex2 );

		logger.debug( "Agent device: " + agentDeviceId + ", VLAN: " + vlan );

		request.setOption( InetRadiusProcessor.AGENT_REMOTE_ID, agentDeviceId );
		request.setOption( InetRadiusProcessor.VLAN_ID, vlan );
	}

	@Override
	public void preprocessAccessRequest( final RadiusPacket request, final RadiusPacket response, final ConnectionSet connectionSet )
		throws Exception
	{
		super.preprocessAccessRequest( request, response, connectionSet );
		// устанавливаем поле username
		processUsername( request );
	}

	@Override
	public void postprocessAccessRequest( final RadiusPacket request, final RadiusPacket response, final ConnectionSet connectionSet )
		throws Exception
	{
		super.postprocessAccessRequest( request, response, connectionSet );
	}

	@Override
	protected void preprocessAccountingRequestImpl( final int acctStatusType, final RadiusPacket request, final RadiusPacket response,
													final ConnectionSet connectionSet )
		throws Exception
	{
		super.preprocessAccountingRequestImpl( acctStatusType, request, response, connectionSet );

		switch( acctStatusType )
		{
		// если сервисный аккаунтинг
			case 101:
			case 102:
			case 103:
			{
				processUsername( request );
			}
				break;

			default:
			{
				processUsername( request );
			}
				break;
		}

		final byte[] address = request.getByteAttribute( -1, RadiusDictionary.Framed_IP_Address, null );
		final byte[] netmask = request.getByteAttribute( -1, RadiusDictionary.Framed_IP_Netmask, null );

		int mask = 32 - IpNet.maskToInt( netmask );

		String subnet = IpNet.toString( address, mask );
		// добавляем подсеть в Called-Station-Id для наглядности
		request.setAttribute( new RadiusAttribute<String>( -1, RadiusDictionary.Called_Station_Id, -1, subnet ) );

		// убираем, чтобы для DOT1Q сессий не был указан IP-адрес/сеть
		request.removeAttributes( -1, RadiusDictionary.Framed_IP_Address );
		request.removeAttributes( -1, RadiusDictionary.Framed_IP_Netmask );

		// и добавляем его как Framed-Route, чтобы можно было обсчитывать по Netflow
		request.setAttribute( new RadiusAttribute<String>( -1, RadiusDictionary.Framed_Route, -1, subnet ) );
	}

	@Override
	public void beforeAuthentication( ServerContext context, RadiusListenerWorker<InetNas> req, RadiusSession<InetNas, InetRadiusSessionParams> radiusSession,
									  RadiusPacket request, RadiusPacket response )
		throws Exception
	{}

	@Override
	public void afterAuthorization( ServerContext conext, RadiusListenerWorker<InetNas> req, RadiusSession<InetNas, InetRadiusSessionParams> radiusSession,
									RadiusPacket request, RadiusPacket response )
		throws Exception
	{}

	/**
	 * Переопределяем выдачу адресов, чтобы подставить Framed-IP-Address и Framed-IP-Netmask из сервиса, т.к. выдача адреса отключена, чтобы нормально выдавать эти адреса по DHCP.
	 */
	@Override
	public boolean addResponseAttributes( ServerContext context, InetServType inetServType, InetServ inetServ, RadiusPacket response, String realm,
										  Map<String, RadiusAttributeSet> realmAttributeMap, RadiusAttributeSet inetServAttributes, Set<Integer> optionSet )
		throws Exception
	{
		byte[] addressFrom = null;
		byte[] addressTo = null;

		switch( inetServType.getAddressType() )
		{
			case DYNAMIC_OR_RANGE:
			case DYNAMIC_OR_SINGLE:
			{
				addressFrom = inetServ.getAddressFrom();
				addressTo = inetServ.getAddressTo();

				if ( addressFrom != null )
				{
					if ( addressTo == null )
					{
						addressTo = addressFrom;
					}

					break;
				}
			}

			// если указана динамическая выдача, это означает что у абонента только один адрес и он его уже получил по DHCP
			case DYNAMIC:
			{
				InetConnection connection = getConnection( inetServ.getId() );
				if ( connection != null )
				{
					addressFrom = addressTo = connection.getInetAddressBytes();
				}

				break;
			}

			default:
			{
				addressFrom = inetServ.getAddressFrom();
				addressTo = inetServ.getAddressTo();

				if ( addressTo == null )
				{
					addressTo = addressFrom;
				}

				break;
			}
		}

		if ( addressFrom != null && addressTo != null )
		{
			IpNet ipNet = IpNet.newInstance( addressFrom, addressTo );
			int netmask = (int)((0xFFFFFFFFl << (32 - ipNet.getMask())) & 0xFFFFFFFFl);

			response.setAttribute( new RadiusAttribute.RadiusAttributeIpAddr( -1, RadiusDictionary.Framed_IP_Address, Utils.convertBytesToInt( ipNet.getSubnet() ) ) );
			response.setAttribute( new RadiusAttribute.RadiusAttributeIpAddr( -1, RadiusDictionary.Framed_IP_Netmask, netmask ) );
		}
		else
		{
			logger.error( "No inetServ address/net found" );
		}

		// выдаем остальные атрибуты как обычно
		return false;
	}

	private InetConnection getConnection( int servId )
	{
		final List<InetConnectionRuntime> connectionRuntimeList = access.connectionManager.getByServId( servId );
		if ( connectionRuntimeList != null )
		{
			for ( int i = 0, size = connectionRuntimeList.size(); i < size; i++ )
			{
				final InetConnection connection = connectionRuntimeList.get( i ).connection;
				// у DHCP-соединений username==null
                if ( ((connection.getType() & InetConnection.TYPE_DHCPv4) != 0 || Utils.isBlankString( connection.getUsername() ))
                    && (connection.getConnectionStatus() == InetConnection.STATUS_ALIVE
                        || connection.getConnectionStatus() == InetConnection.STATUS_SUSPENDED) )
                {
					return connection;
				}
			}
		}

		return null;
	}

	@Override
	public void postprocessDhcpRequest( DhcpPacket request, DhcpPacket response )
		throws Exception
	{}
}
