package ru.bitel.bgbilling.modules.tv.dyn.ares.iptvportal;

import java.io.IOException;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import bitel.billing.server.contract.bean.Contract;
import bitel.billing.server.contract.bean.ContractManager;
import jakarta.annotation.Resource;
import ru.bitel.bgbilling.apps.tv.access.TvAccess;
import ru.bitel.bgbilling.apps.tv.access.om.AbstractOrderEvent;
import ru.bitel.bgbilling.apps.tv.access.om.AccountOrderEvent;
import ru.bitel.bgbilling.apps.tv.access.om.AccountOrderEvent.AccountEntry;
import ru.bitel.bgbilling.apps.tv.access.om.ProductOrderEvent;
import ru.bitel.bgbilling.common.BGException;
import ru.bitel.bgbilling.kernel.container.managed.ServerContext;
import ru.bitel.bgbilling.kernel.contract.api.server.bean.ContractDao;
import ru.bitel.bgbilling.kernel.module.common.bean.User;
import ru.bitel.bgbilling.modules.tv.common.bean.TvAccount;
import ru.bitel.bgbilling.modules.tv.common.bean.TvDevice;
import ru.bitel.bgbilling.modules.tv.common.bean.TvDeviceType;
import ru.bitel.bgbilling.modules.tv.common.om.OrderManager;
import ru.bitel.bgbilling.modules.tv.common.om.OrderManagerAdapter;
import ru.bitel.bgbilling.modules.tv.server.runtime.ProductSpecRuntime;
import ru.bitel.bgbilling.modules.tv.server.runtime.TvAccountSpecRuntime;
import ru.bitel.bgbilling.modules.tv.server.runtime.TvAccountSpecRuntimeMap;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Utils;
import ru.bitel.oss.kernel.entity.common.bean.EntityAttrList;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductSpec;
import ru.bitel.oss.systems.inventory.service.common.bean.ServiceSpec;

public class IptvPortalOrderManager
    extends OrderManagerAdapter
    implements OrderManager
{
	private static final Logger logger = LogManager.getLogger();

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

	private IptvPortalJsonClient jsonClient;

	/**
	 * Полная или частичная синхронизация продуктов
	 */
	private boolean productSyncMode;

	private boolean serviceMode;

	private ContractDao contractDao;
	private ContractManager contractManager;

	private int customerTypePid;
	private int customerTypePidCorporate;

	private long customerTypeIndividual;
	private long customerTypeCorporate;

	private int customerLastNamePid;
	private int customerFirstNamePid;
	private int customerCompanyPid;
	
	/**
	 * Пакеты, включаемые по умолчанию.
	 */
	private Set<String> defaultPackages;
	
	private boolean terminalRegistered;
	
	private TvAccountSpecRuntimeMap tvAccountSpecRuntimeMap;
	
	private int maxTerminal;

	@Override
	public Object init( ServerContext ctx, int moduleId, TvDevice tvDevice, TvDeviceType tvDeviceType, ParameterMap config )
	    throws Exception
	{
		super.init( ctx, moduleId, tvDevice, tvDeviceType, config );

		logger.info( "init" );

		this.jsonClient = IptvPortalJsonClient.newInstance( tvDevice, config );

		this.productSyncMode = config.getInt( "om.product.syncMode", 1 ) > 0;
		this.serviceMode = config.getInt( "om.product.serviceMode", 0 ) > 0;

		this.customerTypePid = config.getInt( "customer.type.pid", 0 );
		this.customerTypePidCorporate = config.getInt( "customer.type.pid.corporate", 0 );
		this.customerTypeIndividual = config.getInt( "customer.type.individual", 1 );
		this.customerTypeCorporate = config.getInt( "customer.type.corporate", 2 );
		this.customerLastNamePid = config.getInt( "customer.lastName.pid", 0 );
		this.customerFirstNamePid = config.getInt( "customer.firstName.pid", 0 );
		this.customerCompanyPid = config.getInt( "customer.company.pid", 0 );
		
		this.defaultPackages = Utils.toSet( config.get( "om.defaultPackages", null ) );
		
		this.terminalRegistered = config.getInt( "om.terminal.registered", 0 ) > 0;
		
		this.tvAccountSpecRuntimeMap = TvAccountSpecRuntimeMap.getInstance( ctx.getConnectionSet(), moduleId );
		
		this.maxTerminal = config.getInt( "om.terminal.max", 3 );

		return null;
	}

	private Map<String, Integer> packageTitleMap = null;
	
	private Integer getPackageId( final Integer productSpecId )
		throws JSONException, IOException, BGException
	{
		ProductSpecRuntime productSpecRuntime = access.productSpecRuntimeMap.get( productSpecId );
		if( productSpecRuntime == null )
		{
			return null;
		}

		ProductSpec productSpec = productSpecRuntime.getProductSpec();

		return getPackageId( productSpec.getIdentifier() );
	}

	private Integer getPackageId( final String identifier )
	    throws JSONException, IOException, BGException
	{
		if( packageTitleMap == null )
		{
			packageTitleMap = new HashMap<String, Integer>();

			JSONObject res = jsonClient.select( "package", "id, name", null );
			if( res.has( "result" ) )
			{
				JSONArray array = res.getJSONArray( "result" );
				for( int i = 0, size = array.length(); i < size; i++ )
				{
					JSONArray row = array.getJSONArray( i );

					int id = row.getInt( 0 );
					String name = row.getString( 1 );

					packageTitleMap.put( name, id );
				}
			}
		}

		final Integer result = packageTitleMap.get( identifier );
		if( result == null )
		{
			logger.error( "Package \"" + identifier + "\" not found" );
		}

		return result;
	}

	@Override
	public Object connect( ServerContext ctx )
	    throws Exception
	{
		super.connect( ctx );

		final Connection con = ctx.getConnection();
		contractDao = new ContractDao( con, User.USER_SERVER );
		contractManager = new ContractManager( con );

		if( !this.jsonClient.authorize() )
		{
			return false;
		}
		
		return null;
	}

	@Override
	public Object disconnect( ServerContext ctx )
	    throws Exception
	{
		if( jsonClient != null )
		{
			jsonClient.disconnect();
		}
		
		packageTitleMap = null;

		return super.disconnect( ctx );
	}

	@Override
	public Object accountCreate( final AccountOrderEvent e, final ServerContext ctx )
	    throws Exception
	{
		logger.info( "accountCreate" );

		return accountModify( e, ctx );
	}

	@Override
	public Object accountModify( AccountOrderEvent e, ServerContext ctx )
	    throws Exception
	{
		logger.info( "accountModify" );

		final int subscriberId = accountModify0( e, ctx );

		for( AccountEntry child : e.getChildrenEntryList() )
		{
			terminalModify( subscriberId, child, ctx );
		}

		productsModifySyncFull( subscriberId, e, ctx );

		return null;
	}

	private int accountModify0( AccountOrderEvent e, ServerContext ctx )
	    throws Exception
	{
		logger.info( "accountModify0" );

		final int contractId = e.getContractId();
		final TvAccount tvAccount = e.getNewTvAccount();

		final Contract contract = contractManager.getContractById( contractId );

		long typeId = customerTypeIndividual;
		if ( customerTypePid > 0 )
		{
			if ( contractDao.getContractParameterList( contractId, customerTypePid ).map( EntityAttrList::getValue ).orElse( 0 ) == customerTypePidCorporate )
			{
				typeId = customerTypeCorporate;
			}
		}
		else
		{
			if( contract.getFc() == 1 )
			{
				typeId = customerTypeCorporate;
			}
		}

        String lastName = "";
        String firstName = "";
        String middleName = "";
        if ( typeId == customerTypeIndividual )
        {
            lastName = customerLastNamePid > 0 ? contractDao.optContractParameterTextAsString( contractId, customerLastNamePid ).orElse( null ) : contract.getComment();

            if ( customerFirstNamePid > 0 )
            {
                firstName = contractDao.optContractParameterTextAsString( contractId, customerFirstNamePid ).orElse( null );
            }
            else if ( lastName != null )
            {
                String[] name = lastName.split( "\\s+" );
                if ( name.length >= 2 )
                {
                    lastName = name[ 0 ];
                    firstName = name[ 1 ];

                    if ( name.length >= 3 )
                    {
                        middleName = name[ 2 ];
                    }
                }
            }
        }
        else
        {
            if ( customerCompanyPid > 0 )
            {
                lastName = contractDao.optContractParameterTextAsString( contractId, customerCompanyPid )
                	.orElse( contract.getComment() );
            }
        }

		int subscriberId;
		int maxTerminal = this.maxTerminal;
		
		final TvAccountSpecRuntime tvAccountSpecRuntime = tvAccountSpecRuntimeMap.get( tvAccount.getSpecId() );
		if( tvAccountSpecRuntime != null )
		{
			maxTerminal = tvAccountSpecRuntime.config.getInt( "om.terminal.max", maxTerminal );
		}

		if( e.getOldTvAccount() == null || Utils.isBlankString( e.getOldTvAccount().getDeviceAccountId() ) )
		{
			logger.debug( e.getOldTvAccount() );

			subscriberId = jsonClient.insert( "subscriber", "username, password, max_terminal, email, language_id, disabled, first_name, middle_name, surname",
			                                  tvAccount.getLogin(), tvAccount.getPassword(), maxTerminal, "", 1, e.getNewState() != TvAccount.STATE_ENABLE, firstName, middleName, lastName );

			if( subscriberId <= 0 )
			{
				subscriberId = jsonClient.update( "subscriber", "username", tvAccount.getLogin(), "username, password, max_terminal, email, language_id, disabled, first_name, middle_name, surname",
				                                  tvAccount.getLogin(), tvAccount.getPassword(), maxTerminal, "", 1, e.getNewState() != TvAccount.STATE_ENABLE, firstName, middleName, lastName );
			}

			e.getEntry().setDeviceAccountId( String.valueOf( subscriberId ) );
		}
		else
		{
			subscriberId = Utils.parseInt( e.getOldTvAccount().getDeviceAccountId() );

			jsonClient.update( "subscriber", "id", String.valueOf( subscriberId ), "username, password, max_terminal, email, language_id, disabled, first_name, middle_name, surname",
			                   tvAccount.getLogin(), tvAccount.getPassword(), maxTerminal, "", 1, e.getNewState() != TvAccount.STATE_ENABLE, firstName, middleName, lastName );

			if( subscriberId <= 0 )
			{
				subscriberId = jsonClient.insert( "subscriber", "username, password, max_terminal, email, language_id, disabled, first_name, middle_name, surname",
				                                  tvAccount.getLogin(), tvAccount.getPassword(), maxTerminal, "", 1, e.getNewState() != TvAccount.STATE_ENABLE, firstName, middleName, lastName );

				e.getEntry().setDeviceAccountId( String.valueOf( subscriberId ) );
			}
		}

		return subscriberId;
	}

	@Override
	public Object accountRemove( final AccountOrderEvent e, final ServerContext ctx )
	    throws Exception
	{
		logger.info( "accountRemove" );

		final TvAccount tvAccount = e.getOldTvAccount();
		final int subscriberId = Utils.parseInt( tvAccount.getDeviceAccountId() );

		if( subscriberId <= 0 )
		{
			logger.info( "subscriberId=" + subscriberId );
			return null;
		}

		jsonClient.remove( "subscriber", "id", String.valueOf( subscriberId ) );

		return null;
	}

	@Override
	public Object accountStateModify( final AccountOrderEvent e, final ServerContext ctx )
	    throws Exception
	{
		if( e.getOldTvAccount() == null || Utils.isBlankString( e.getOldTvAccount().getDeviceAccountId() ) )
		{
			return accountModify( e, ctx );
		}

		final long subscriberId = Utils.parseInt( e.getNewTvAccount().getDeviceAccountId() );

		jsonClient.update( "subscriber", "id", String.valueOf( subscriberId ), "disabled", e.getNewState() != TvAccount.STATE_ENABLE );

		return null;
	}

	@Override
	public Object accountOptionsModify( AbstractOrderEvent e, ServerContext ctx )
		throws Exception
	{
		logger.debug( "accountOptionsModify" );

		final int subscriberId = Utils.parseInt( e.getTvAccountRuntime().getTvAccount().getDeviceAccountId() );

		if( this.productSyncMode )
		{
			return productsModifySyncFull( subscriberId, e, ctx );
		}
		else
		{
            final Set<Integer> packagesToRemove = new HashSet<Integer>();
            final Set<Integer> packagesToAdd = new HashSet<Integer>();

            productSpecIdsToPackages( e.getDeviceOptionIdsToDisable(), packagesToRemove );
            productSpecIdsToPackages( e.getDeviceOptionIdsToEnable(), packagesToAdd );

            return productsModify( subscriberId, packagesToAdd, packagesToRemove );
		}
	}

    private void productSpecIdsToPackages( final Set<Integer> productSpecIds, final Set<Integer> packages )
        throws IOException, BGException
    {
        for( Integer productSpecId : productSpecIds )
        {
            Integer packageId = getPackageId( productSpecId );
            if( packageId != null )
            {
                packages.add( packageId );
            }
        }
    }

	private void terminalModify( final long subscriberId, final AccountEntry entry, final ServerContext ctx )
		throws Exception
	{
		logger.info( "terminalModify" );

		int terminalId = 0;

		if( entry.getNewState() == TvAccount.STATE_DELETED )
		{
			terminalId = Utils.parseInt( entry.getOldTvAccount().getDeviceAccountId() );

			jsonClient.remove( "terminal", "id", String.valueOf( terminalId ) );

			return;
		}

		TvAccount tvAccount = entry.getNewTvAccount() != null ? entry.getNewTvAccount() : entry.getOldTvAccount();

		byte[] macAddress = tvAccount.getMacAddressListBytes();
		
		// возможно терминал с таким mac-адресом уже заведен
		JSONObject res = jsonClient.select( "terminal", "id, subscriber_id",
											new JSONObject( Collections.singletonMap( "eq", Arrays.asList( "mac_addr", Utils.bytesToString( macAddress, true, ":" ) ) ) ) );

		logger.info( String.valueOf( res ) );

		JSONArray array = res.optJSONArray( "result" );
		if( array != null )
		{
			for( int i = 0, size = array.length(); i < size; i++ )
			{
				JSONArray terminal = array.getJSONArray( i );

				int oldTerminalId = terminal.getInt( 0 );
				long oldSubscriberId = terminal.optLong( 1, 0 );

				if( oldSubscriberId <= 0 || oldSubscriberId == subscriberId )
				{
					terminalId = oldTerminalId;
				}
				else
				{
					logger.error( "Terminal with MAC-address already exists, but linked to another subscriber: " + oldSubscriberId );
				}
			}
		}

		if( terminalId == 0 && (entry.getOldTvAccount() == null || Utils.isBlankString( entry.getOldTvAccount().getDeviceAccountId() )) )
		{
			if( Utils.notBlankString( tvAccount.getIdentifier() ) )
			{
				terminalId = jsonClient.insert( "terminal", "subscriber_id, mac_addr, inet_addr, profile_id, registration_id, language_id, disabled, registered",
												subscriberId, Utils.bytesToString( macAddress, true, ":" ), tvAccount.getIdentifier(), 0, "", 1, false, terminalRegistered );
			}
			else
			{
				terminalId = jsonClient.insert( "terminal", "subscriber_id, mac_addr, profile_id, registration_id, language_id, disabled, registered",
												subscriberId, Utils.bytesToString( macAddress, true, ":" ), 0, "", 1, false, terminalRegistered );
			}

			if( terminalId <= 0 )
			{
				terminalId = jsonClient.update( "terminal", "id", String.valueOf( terminalId ), "subscriber_id, mac_addr, profile_id, registration_id, language_id, disabled",
				                                subscriberId, Utils.bytesToString( macAddress, true, ":" ), 0, "", 1, false );
			}

			if( terminalId > 0 )
			{
				entry.setDeviceAccountId( String.valueOf( terminalId ) );
			}
		}
		else
		{
			if( terminalId <= 0 )
			{
				terminalId = Utils.parseInt( entry.getOldTvAccount().getDeviceAccountId() );
			}
			else
			{
				entry.setDeviceAccountId( String.valueOf( terminalId ) );
			}

			//if( entry.getOldState() == TvAccount.STATE_DELETED )

			terminalId =
				jsonClient.update( "terminal", "id", String.valueOf( terminalId ),
								   "subscriber_id, mac_addr, profile_id, registration_id, session_id, language_id, disabled, registered",
								   subscriberId, Utils.bytesToString( macAddress, true, ":" ), 0, "", "", 1, false, terminalRegistered );

			if( terminalId <= 0 )
			{
				if( Utils.notBlankString( tvAccount.getIdentifier() ) )
				{
					terminalId = jsonClient.insert( "terminal", "subscriber_id, mac_addr, inet_addr, profile_id, registration_id, language_id, disabled, registered",
													subscriberId, Utils.bytesToString( macAddress, true, ":" ), tvAccount.getIdentifier(), 0, "", 1, false, terminalRegistered );
				}
				else
				{
					terminalId = jsonClient.insert( "terminal", "subscriber_id, mac_addr, profile_id, registration_id, language_id, disabled, registered",
													subscriberId, Utils.bytesToString( macAddress, true, ":" ), 0, "", 1, false, terminalRegistered );
				}

				if( terminalId > 0 )
				{
					entry.setDeviceAccountId( String.valueOf( terminalId ) );
				}
			}
		}
	}

	private Object productsModifySyncFull( final int subscriberId, final AbstractOrderEvent e, final ServerContext ctx )
	    throws Exception
	{
		logger.debug( "productsModifyFullSync" );

		final Set<Integer> packagesToAdd = new HashSet<Integer>();
		
		for( String identifier : this.defaultPackages )
		{
			Integer packageId = getPackageId( identifier );
			if( packageId != null )
			{
				packagesToAdd.add( packageId );
			}
		}

		if( serviceMode )
		{
			for( ServiceSpec serviceSpec : e.getFullServiceSpecSetToEnable() )
			{
				Integer packageId = getPackageId( serviceSpec.getIdentifier() );
				if( packageId != null )
				{
					packagesToAdd.add( packageId );
				}
			}
		}
		else
		{
			for( ProductSpec productSpec : e.getFullProductSpecSetToEnable() )
			{
				logger.info( "Product: " + productSpec );

				Integer packageId = getPackageId( productSpec.getIdentifier() );
				if( packageId != null )
				{
					packagesToAdd.add( packageId );
				}
			}
			
			productSpecIdsToPackages( e.getNewDeviceOptionIds(), packagesToAdd );
		}

		return productsModifySyncFull( subscriberId, packagesToAdd );
	}

	private Object productsModifySyncFull( final int subscriberId, final Set<Integer> packagesToAdd )
		throws Exception
	{
		packagesToAdd.remove( 0 );

		Set<Integer> currentPackageIds = new HashSet<Integer>();

		JSONObject res = jsonClient.select( "subscriber_package", "package_id", new JSONObject( Collections.singletonMap( "eq", Arrays.asList( "subscriber_id", String.valueOf( subscriberId ) ) ) ) );
		if( res.has( "result" ) )
		{
			JSONArray array = res.getJSONArray( "result" );
			for( int i = 0, size = array.length(); i < size; i++ )
			{
				JSONArray row = array.getJSONArray( i );
				int packageId = row.getInt( 0 );
				currentPackageIds.add( packageId );
			}
		}

		logger.info( "Current packageIds: " + currentPackageIds + ", need packageIds: " + packagesToAdd );

		//jsonClient.remove( "subscriber_package", "subscriber_id", String.valueOf( subscriberId ) );

		Set<Integer> packagesToRemove = new HashSet<Integer>( currentPackageIds );
		packagesToRemove.removeAll( packagesToAdd );

		packagesToAdd.removeAll( currentPackageIds );

		if( packagesToRemove.size() > 0 )
		{
			List<JSONObject> packagesWhere = new ArrayList<JSONObject>();
			for( Integer packageId : packagesToRemove )
			{
				packagesWhere.add( new JSONObject( Collections.singletonMap( "eq", Arrays.asList( "package_id", String.valueOf( packageId ) ) ) ) );
			}

			try
			{
				logger.debug( "remove packages: " + packagesToRemove );

				/*jsonClient.remove( "subscriber_package", new JSONObject( Collections.singletonMap( "and",
				                                                                                   Arrays.asList( new JSONObject( Collections.singletonMap( "eq", Arrays.asList( "subscriber_id", String.valueOf( subscriberId ) ) ) ),
				                                                                                                  new JSONObject( Collections.singletonMap( "eq", Arrays.asList( "package_id", String.valueOf( packageId ) ) ) ) ) ) ) );*/

				jsonClient.remove( "subscriber_package", new JSONObject( Collections.singletonMap( "and",
															Arrays.asList( new JSONObject( Collections.singletonMap( "eq", Arrays.asList( "subscriber_id", String.valueOf( subscriberId ) ) ) ),
																			new JSONObject( Collections.singletonMap( "or", packagesWhere ) ) ) ) ) );
			}
			catch( BGException ex )
			{
				logger.error( "Can't remove packages " + packagesToRemove + ": " + ex.getMessage(), ex );
			}
		}

		for( Integer packageId : packagesToAdd )
		{
			try
			{
				logger.debug( "add package: " + packageId );

				jsonClient.insert( "subscriber_package", "subscriber_id, package_id, expired_on, enabled", subscriberId, packageId, null, true );
			}
			catch( BGException ex )
			{
				logger.error( "Can't add packages " + packageId + ": " + ex.getMessage(), ex );
			}
		}

		return null;
	}

	@Override
	public Object productsModify( final ProductOrderEvent e, final ServerContext ctx )
	    throws Exception
	{
		logger.debug( "productsModify" );

		final int subscriberId = Utils.parseInt( e.getTvAccount().getDeviceAccountId() );

		if( this.productSyncMode )
		{
			return productsModifySyncFull( subscriberId, e, ctx );
		}

		final Set<Integer> packagesToRemove = new HashSet<Integer>();
		final Set<Integer> packagesToAdd = new HashSet<Integer>();

		if( serviceMode )
		{
			for( ServiceSpec serviceSpec : e.getServiceSpecSetToRemove() )
			{
				Integer packageId = getPackageId( serviceSpec.getIdentifier() );
				if( packageId != null )
				{
					packagesToRemove.add( packageId );
				}
			}

			for( ServiceSpec serviceSpec : e.getServiceSpecSetToAdd() )
			{
				Integer packageId = getPackageId( serviceSpec.getIdentifier() );
				if( packageId != null )
				{
					packagesToAdd.add( packageId );
				}
			}
		}
		else
		{
			for( ProductSpec productSpec : e.getProductSpecSetToRemove() )
			{
				Integer packageId = getPackageId( productSpec.getIdentifier() );
				if( packageId != null )
				{
					packagesToRemove.add( packageId );
				}
			}

			for( ProductSpec productSpec : e.getProductSpecSetToAdd() )
			{
				Integer packageId = getPackageId( productSpec.getIdentifier() );
				if( packageId != null )
				{
					packagesToAdd.add( packageId );
				}
			}
			
			productSpecIdsToPackages( e.getDeviceOptionIdsToDisable(), packagesToRemove );
			productSpecIdsToPackages( e.getDeviceOptionIdsToEnable(), packagesToAdd );
		}

		return productsModify( subscriberId, packagesToAdd, packagesToRemove );
	}
	
	private Object productsModify( final int subscriberId, final Set<Integer> packagesToAdd, final Set<Integer> packagesToRemove )
	    throws Exception
	{
		packagesToRemove.remove( 0 );
		packagesToAdd.remove( 0 );
		
		for( String identifier : this.defaultPackages )
		{
			Integer packageId = getPackageId( identifier );
			if( packageId != null )
			{
				packagesToRemove.remove( packageId );
			}
		}

		if( packagesToRemove.size() > 0 )
		{
			List<JSONObject> packagesWhere = new ArrayList<JSONObject>();
			for( Integer packageId : packagesToRemove )
			{
				packagesWhere.add( new JSONObject( Collections.singletonMap( "eq", Arrays.asList( "package_id", String.valueOf( packageId ) ) ) ) );
			}

			try
			{
				logger.debug( "remove packages: " + packagesToRemove );

				jsonClient.remove( "subscriber_package", new JSONObject( Collections.singletonMap( "and",
				           Arrays.asList( new JSONObject( Collections.singletonMap( "eq", Arrays.asList( "subscriber_id", String.valueOf( subscriberId ) ) ) ),
										  new JSONObject( Collections.singletonMap( "or", packagesWhere ) ) ) ) ) );
			}
			catch( BGException ex )
			{
				logger.error( "Can't remove packages " + packagesToRemove + ": " + ex.getMessage(), ex );
			}
		}

		for( Integer packageId : packagesToAdd )
		{
			try
			{
				logger.debug( "add package: " + packageId );

				jsonClient.insert( "subscriber_package", "subscriber_id, package_id, expired_on, enabled", subscriberId, packageId, null, true );
			}
			catch( BGException ex )
			{
				logger.error( "Can't add packageId " + packageId + ": " + ex.getMessage(), ex );
			}
		}

		return null;
	}
}
