package ru.bitel.bgbilling.modules.tv.dyn.nexttvnet;

import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.json.JSONArray;
import org.json.JSONObject;

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.ProductOrderEvent;
import ru.bitel.bgbilling.kernel.container.managed.ServerContext;
import ru.bitel.bgbilling.kernel.contract.api.common.bean.Contract;
import ru.bitel.bgbilling.kernel.contract.api.common.bean.enums.ContractFace;
import ru.bitel.bgbilling.kernel.contract.api.server.bean.ContractDao;
import ru.bitel.bgbilling.kernel.event.EventProcessor;
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.event.TvAccountModifiedEvent;
import ru.bitel.bgbilling.modules.tv.common.om.OrderManager;
import ru.bitel.bgbilling.modules.tv.common.om.OrderManagerAdapter;
import ru.bitel.bgbilling.modules.tv.dyn.JsonClient;
import ru.bitel.bgbilling.modules.tv.dyn.JsonClient.Method;
import ru.bitel.bgbilling.modules.tv.dyn.TvDynUtils;
import ru.bitel.bgbilling.modules.tv.server.bean.TvAccountDao;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Utils;
import ru.bitel.common.sql.ConnectionSet;
import ru.bitel.oss.kernel.entity.common.bean.EntityAttrText;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductSpec;
import ru.bitel.oss.systems.inventory.service.common.bean.ServiceSpec;

public class NextTvOrderManager
	extends OrderManagerAdapter
	implements OrderManager
{
	@Resource(name = "access")
	private TvAccess access;

	private int moduleId;

	private JsonClient jsonClient;
	private ContractDao contractDao;
	private TvAccountDao tvAccountDao;

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

	/**
	 * Если true - синхронизация на уровне сервисов, а не продуктов.
	 */
	private boolean serviceMode;

	/**
	 * Тарифный план по умолчанию
	 */
	private long tariffDefault;

	private int customerCompanyPid;

	private int customerFirstNamePid;
	private int customerMiddleNamePid;
	private int customerLastNamePid;

//	private int customerAddressPid;
	private int customerPhonePid;
	private int customerEmailPid;

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

		getLogger().info( "init" );

		this.moduleId = moduleId;

		String host = null;
		int port = 0;

		List<InetSocketAddress> addressList = tvDevice.getHosts();
		if ( addressList.size() > 0 )
		{
			InetSocketAddress socketAddress = addressList.get( 0 );
			host = socketAddress.getAddress().getHostAddress();
			port = socketAddress.getPort();
		}

		if ( Utils.isBlankString( host ) )
		{
			host = "devagent.nexttvnet.ru";
		}

		if ( port <= 0 )
		{
			port = 443;
		}

		URL url = URI.create( config.get( "om.url", config.get( "nexttv.api.url", "https://" + host + ":" + port + "/iptv-portal/backend/api/" ) ) ).toURL();

		String login = config.get( "om.login", config.get( "nexttv.api.login", tvDevice.getUsername() ) );
		String password = config.get( "om.password", config.get( "nexttv.api.password", tvDevice.getPassword() ) );

		this.jsonClient = new JsonClient( url, login, password );

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

		this.tariffDefault = config.getLong( "om.tariff.default", 23 );

		int customerNamePid = config.getInt( "customer.name.pid", 0 );
		customerLastNamePid = config.getInt( "customer.lastName.pid", customerNamePid );
		customerFirstNamePid = config.getInt( "customer.firstName.pid", 0 );
		customerMiddleNamePid = config.getInt( "customer.middleName.pid", 0 );
		customerCompanyPid = config.getInt( "customer.company.pid", customerNamePid );

//		customerAddressPid = config.getInt( "customer.address.pid", 0 );
		customerPhonePid = config.getInt( "customer.phone.pid", 0 );
		customerEmailPid = config.getInt( "customer.email.pid", 0 );

		return null;
	}

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

	private ConnectionSet connectionSet;

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

		connectionSet = ctx.getConnectionSet();
		contractDao = new ContractDao( connectionSet.getConnection(), User.USER_SERVER );
		tvAccountDao = new TvAccountDao( connectionSet.getConnection(), moduleId );

		return null;
	}

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

		if ( jsonClient != null )
		{
			jsonClient.disconnect();
		}

		return super.disconnect( ctx );
	}

	private static JSONObject value( String value )
	{
		return new JSONObject().put( "value", value );
	}

	private static JSONObject essential( int typeId, String value )
	{
		return new JSONObject().put( "essential_type_id", typeId ).put( "value", value );
	}

	private static JSONObject addressEntry( int typeId, String value )
	{
		return new JSONObject().put( "address_entry_type_id", typeId ).put( "value", value );
	}

	private static JSONObject contact( int typeId, String value )
	{
		return new JSONObject().put( "contact_type_id", typeId ).put( "value", value );
	}

	private static JSONArray addressEntries( String index, String area, String city, String street, String house, String frac, String flat )
	{
		JSONArray result = new JSONArray();

		result.put( addressEntry( 1, index ) );
		result.put( addressEntry( 2, area ) );
		result.put( addressEntry( 3, city ) );
		result.put( addressEntry( 4, street ) );
		result.put( addressEntry( 5, house ) );
		result.put( addressEntry( 6, frac ) );
		result.put( addressEntry( 7, flat ) );

		return result;
	}

	/**
	 * Получение contractorId с созданием contractor'а если еще не создан.
	 * @param e
	 * @param ctx
	 * @param update
	 * @return
	 * @throws Exception
	 */
	private int getContractorId( AccountOrderEvent e, ServerContext ctx, boolean update )
		throws Exception
	{
		final int contractId = e.getContractId();
		String contractorIdString = contractDao.optContractParameterTextAsString( contractId, 41 ).orElse( "0" );

		int contractorId = Utils.parseInt( contractorIdString );
		if ( !update && contractorId > 0 )
		{
			return contractorId;
		}

		final Contract contract = contractDao.get( contractId );

		JSONObject contractor = new JSONObject();

		if ( contractorId > 0 )
		{
			contractor.put( "id", contractorIdString );
		}

		String phone = contractDao.optContractParameterTextAsString( contract.getId(), customerPhonePid ).orElse( null );
		if ( phone == null )
		{
			//phone = contractParameterManager.getPhoneParamStringValue( contract.getId(), customerPhonePid );
		}

		if ( Utils.isBlankString( phone ) )
		{
			phone = "+79171234567";
		}

		String email = contractDao.getEmailFromContractParameter( contract.getId(), customerEmailPid );
		if ( Utils.isBlankString( email ) )
		{
			email = "none@none.com";
		}

		// юр лицо?
		if ( contract.getFace() == ContractFace.URIC )
		{
			final String name = TvDynUtils.getName( contractDao, contract, customerCompanyPid );

			// юр. лицо
			contractor.put( "contractor_type_id", 3 );

			JSONObject company = new JSONObject();
			// форма собственности
			company.put( "ownership_form_id", 1 );
			// название
			company.put( "name", Utils.maskBlank( name, Utils.maskBlank( contract.getTitle(), "undef" ) ) );

			contractor.put( "company", company );

			// реквизиты, обязателен
			JSONArray essentials = new JSONArray();

			// ИНН
			essentials.put( essential( 2, "" ) );
			// КПП
			essentials.put( essential( 3, "" ) );
			// Расчетный счет
			essentials.put( essential( 4, "" ) );
			// Корреспондетский счет
			essentials.put( essential( 5, "" ) );
			// БИК
			essentials.put( essential( 6, "" ) );
			// Банк
			essentials.put( essential( 7, "" ) );

			company.put( "essentials", essentials );

			JSONArray addresses = new JSONArray();

			JSONArray addressTypeIds = new JSONArray();
			// юр. адрес
			addressTypeIds.put( 1 );
			// факт. адрес
			addressTypeIds.put( 2 );

			JSONObject address = new JSONObject();
			address.put( "addressTypeIds", addressTypeIds );
			address.put( "addressEntries", addressEntries( "123456", "Москва", "Москва", "Хуторская", "28", "v", "226" ) );

			addresses.put( address );

			company.put( "addresses", addresses );

			// необязателен
			JSONArray companyContactInfos = new JSONArray();
			// телефон
			companyContactInfos.put( contact( 1, phone ) );
			// email
			companyContactInfos.put( contact( 3, email ) );

			company.put( "companyContactInfos", companyContactInfos );
		}
		else
		{
			final String[] name = TvDynUtils.getName( contractDao, contract, customerLastNamePid, customerFirstNamePid, customerMiddleNamePid );

			// физ. лицо
			contractor.put( "contractor_type_id", 4 );

			JSONObject person = new JSONObject();
			person.put( "surname", value( name[1] ) );
			person.put( "name", value( name[2] ) );
			person.put( "patronymic", value( name[3] ) );

			contractor.put( "person", person );

			// реквизиты, обязателен
			JSONArray essentials = new JSONArray();

			/*essentials.put( essential( 9, "11.12.1980" ) );
			essentials.put( essential( 10, "1234" ) );
			essentials.put( essential( 11, "123456" ) );
			essentials.put( essential( 12, "ОВД" ) );
			essentials.put( essential( 13, "11.12.1990" ) );
			essentials.put( essential( 14, "772-039" ) );*/

			// дата рождения
			essentials.put( essential( 9, "" ) );
			// серия паспорта
			essentials.put( essential( 10, "" ) );
			// номер паспорта
			essentials.put( essential( 11, "" ) );
			// кем выдан
			essentials.put( essential( 12, "" ) );
			// дата выдачи паспорта
			essentials.put( essential( 13, "" ) );
			// код подразделения
			essentials.put( essential( 14, "" ) );

			person.put( "essentials", essentials );

			JSONArray addresses = new JSONArray();

			JSONArray addressTypeIds = new JSONArray();
			addressTypeIds.put( 3 );

			JSONObject address = new JSONObject()
			    .put( "addressTypeIds", addressTypeIds )
			    .put( "addressEntries", addressEntries( "123456", "Москва", "Москва", "Хуторская", "28", "v", "226" ) );

			addresses.put( address );

			person.put( "addresses", addresses );

			// необязателен
			JSONArray personContactInfos = new JSONArray()
			    // мобильный телефон
			    .put( contact( 1, phone ) )
			    // email
			    .put( contact( 3, email ) );

			person.put( "personContactInfos", personContactInfos );
		}

		JSONObject result = jsonClient.invoke( Method.post, null, "contractor", null, contractor );
		if ( getLogger().isDebugEnabled() )
		{
		    getLogger().debug( result.toString() );
		}

		if ( !update )
		{
			contractorIdString = result.getJSONObject( "data" ).getJSONObject( "contractor" ).getString( "id" );
			contractorId = Utils.parseInt( contractorIdString );

			contractDao.updateContractParameter( contractId, new EntityAttrText( contractId, 41, contractorIdString ) );
		}

		return contractorId;
	}

	private long[] parseDeviceAccountId( String deviceAccountId )
	{
		if ( Utils.isBlankString( deviceAccountId ) )
		{
			return null;
		}

		String[] params = deviceAccountId.split( "-" );
		if ( params.length < 2 )
		{
			return null;
		}

		long contractId = Utils.parseInt( params[0] );
		long abonentAccountId = Utils.parseInt( params[1] );

		if ( contractId <= 0 || abonentAccountId <= 0 )
		{
			return null;
		}

		return new long[] { contractId, abonentAccountId };
	}

	/**
	 * Редактирование и создание аккаунта-контракта.
	 * @param e
	 * @param ctx
	 * @param contractorId
	 * @return
	 * @throws Exception
	 */
	private long[] accountModify0( AccountOrderEvent e, ServerContext ctx, int contractorId )
		throws Exception
	{
		long contractId;
		long abonentAccountId;

		if ( e.getOldTvAccount() == null || Utils.isBlankString( e.getOldTvAccount().getDeviceAccountId() ) )
		{
			JSONObject contract = new JSONObject();
			contract.put( "contractor_id", contractorId );
			contract.put( "tariff_id", tariffDefault );
			contract.put( "email", "none@none.no" );

			JSONObject result = jsonClient.invoke( Method.post, null, "contract", null, contract );

			contractId = result.getJSONObject( "data" ).getJSONObject( "contract" ).getInt( "id" );

			JSONObject abonentAccount = result.getJSONObject( "data" ).getJSONObject( "contract" ).getJSONObject( "abonentAccount" );
			abonentAccountId = abonentAccount.getInt( "id" );

			String username = abonentAccount.getString( "username" );
			String password = abonentAccount.getString( "password" );

			TvAccount tvAccount = tvAccountDao.get( e.getNewTvAccount().getId() );
			TvAccount oldTvAccount = tvAccount.clone();
			if ( tvAccount != null )
			{
				tvAccount.setLogin( username );
				tvAccount.setPassword( password );
				tvAccount.setTitle( "Аккаунт: " + username );
				tvAccountDao.update( tvAccount );

				EventProcessor.getInstance().publish( new TvAccountModifiedEvent( moduleId, e.getContractId(), 0, oldTvAccount, tvAccount ) );
			}

			e.getEntry().setDeviceAccountId( contractId + "-" + abonentAccountId );

			accountStateModify0( e, abonentAccountId );
		}
		else
		{
			long[] deviceAccountId = parseDeviceAccountId( e.getOldTvAccount().getDeviceAccountId() );
			contractId = deviceAccountId[0];
			abonentAccountId = deviceAccountId[1];

			JSONObject result = jsonClient.invoke( Method.get, null, "abonentAccount", String.valueOf( abonentAccountId ), null );

			if ( result.getBoolean( "success" ) )
			{
				JSONObject abonentAccount = result.getJSONObject( "data" );

				String username = abonentAccount.getString( "username" );
				String password = abonentAccount.getString( "password" );

				if ( !username.equals( e.getNewTvAccount().getLogin() ) || !password.equals( e.getNewTvAccount().getPassword() ) )
				{
					TvAccount tvAccount = tvAccountDao.get( e.getNewTvAccount().getId() );
					TvAccount oldTvAccount = tvAccount.clone();
					if ( tvAccount != null )
					{
						tvAccount.setLogin( username );
						tvAccount.setPassword( password );
						tvAccount.setTitle( "Аккаунт: " + username );
						tvAccountDao.update( tvAccount );

						EventProcessor.getInstance().publish( new TvAccountModifiedEvent( moduleId, e.getContractId(), 0, oldTvAccount, tvAccount ) );
					}
				}
			}
			else
			{
				getLogger().error( "AbonentAccount not found with id=" + abonentAccountId );
			}

			if ( e.getOldState() != e.getNewState() )
			{
				accountStateModify0( e, abonentAccountId );
			}
		}

		return new long[] { contractId, abonentAccountId };
	}

	private void accountStateModify0( final AccountOrderEvent e, final long abonentAccountId )
		throws Exception
	{
		JSONObject id = new JSONObject()
		    .put( "id", abonentAccountId );

		if ( e.getNewState() == TvAccount.STATE_ENABLE )
		{
			jsonClient.invoke( Method.post, null, "abonentAccount/unblock", null, id );
		}
		else
		{
			jsonClient.invoke( Method.post, null, "abonentAccount/block", null, id );
		}
	}

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

		return accountModify( e, ctx );
	}

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

		int contractorId = getContractorId( e, ctx, true );
		getLogger().info( "contractorId " + contractorId );

		long[] deviceAccountId = accountModify0( e, ctx, contractorId );
		getLogger().info( "contractId " + deviceAccountId[0] );
		getLogger().info( "abonentAccountId " + deviceAccountId[1] );

		try
		{
			// синхронизируем все продукты
			productsModifySyncFull( e, ctx, deviceAccountId[1] );
		}
		catch( Exception ex )
		{
			logError( ex );
		}

		return null;
	}

	/**
	 * Удаление аккаунта.
	 * @param e
	 * @param ctx
	 * @return
	 * @throws Exception
	 */
	@Override
	public Object accountRemove( final AccountOrderEvent e, final ServerContext ctx )
		throws Exception
	{
	    getLogger().info( "accountRemove" );

		String deviceAccountIdString = e.getOldTvAccount().getDeviceAccountId();
		long[] deviceAccountId = parseDeviceAccountId( deviceAccountIdString );
		// если deviceAccountId пустой, то у нас нет привязки к аккаунту в MW - возможно он просто не был создан
		if ( deviceAccountId == null )
		{
		    getLogger().info( "deviceAccountId=" + deviceAccountIdString + ". Skip remove." );
			return null;
		}

		long contractId = deviceAccountId[0];

		if ( contractId <= 0 )
		{
		    getLogger().info( "mwContractId=" + contractId );
			return null;
		}

		// удаляем контракт по id
		jsonClient.invoke( Method.delete, null, "contract", String.valueOf( contractId ), null );

		return null;
	}

	/*
	{
	  "success": true,
	  "message": "Запись найдена",
	  "data": {
	    "id": 1106,
	    "contract_id": 1155,
	    "middleware_id": 1,
	    "username": "test1000",
	    "password": "60597551",
	    "email": "none@none.no",
	    "blocked": "1",
	    "create_time": "2015-06-08 15:48:19",
	    "update_time": "2015-06-25 18:04:24",
	    "serviceSubscriptions": [
	      {
	        "id": 10421,
	        "abonent_account_id": 1106,
	        "service_id": 2,
	        "tariff_id": 23,
	        "create_time": "2015-06-08 15:48:19",
	        "update_time": "2015-06-08 15:48:19"
	      },
	      {
	        "id": 10422,
	        "abonent_account_id": 1106,
	        "service_id": 1,
	        "tariff_id": 23,
	        "create_time": "2015-06-08 15:48:19",
	        "update_time": "2015-06-08 15:48:19"
	      }
	    ]
	  }
	}
	*/

	/**
	 * Полная синхронизация продуктов/пакетов.
	 * @param e
	 * @param ctx
	 * @param abonentAccountId
	 * @return
	 * @throws Exception
	 */
	private Object productsModifySyncFull( final AbstractOrderEvent e, final ServerContext ctx, final long abonentAccountId )
		throws Exception
	{
	    getLogger().debug( "productsModifyFullSync" );

		final Set<Long> servicesToAdd = new HashSet<Long>();

		if ( serviceMode )
		{
			// получаем полный список активных сервисов
			for( ServiceSpec serviceSpec : e.getFullServiceSpecSetToEnable() )
			{
				servicesToAdd.add( Utils.parseLong( serviceSpec.getIdentifier().trim() ) );
			}
		}
		else
		{
			// получаем список активных продуктов
			for( ProductSpec productSpec : e.getFullProductSpecSetToEnable() )
			{
			    getLogger().info( "Product: " + productSpec );

				servicesToAdd.add( Utils.parseLong( productSpec.getIdentifier().trim() ) );
			}

			// добавляем продукты-опции
			for( ProductSpec productSpec : e.getNewDeviceOptionProductSpecs() )
			{
			    getLogger().info( "Product (option): " + productSpec );

				servicesToAdd.add( Utils.parseLong( productSpec.getIdentifier().trim() ) );
			}
		}

		// удаляем некорректные записи
		servicesToAdd.remove( 0L );

		// текущие подписки ID сервиса-пакета <-> ID записи привязки сервиса-пакета к контракту
		Map<Long, Long> currentServiceIds = new HashMap<Long, Long>();

		// получаем список текущих активных сервисов
		JSONObject result = jsonClient.invoke( Method.get, null, "abonentAccount", String.valueOf( abonentAccountId ), null );
		JSONObject abonentAccount = result.getJSONObject( "data" );
		JSONArray serviceSubscriptions = abonentAccount.getJSONArray( "serviceSubscriptions" );

		for( int i = 0, size = serviceSubscriptions.length(); i < size; i++ )
		{
			JSONObject serviceSubscription = serviceSubscriptions.getJSONObject( i );

			// id записи привязки сервиса-пакета MW
			long serviceSubscriptionId = serviceSubscription.getLong( "id" );
			//serviceSubscription.getLong( "tariff_id" );

			// id сервиса-пакета MW
			long serviceId = serviceSubscription.getLong( "service_id" );

			currentServiceIds.put( serviceId, serviceSubscriptionId );
		}

		getLogger().info( "Current serviceIds: " + currentServiceIds + ", need serviceIds: " + servicesToAdd );

		// удаляем те, что неактивны в биллинге, но есть в текущих
		for( Map.Entry<Long, Long> entry : currentServiceIds.entrySet() )
		{
			if ( !servicesToAdd.contains( entry.getKey() ) )
			{
			    getLogger().debug( "delete serviceSubscription: " + entry.getKey() + " - " + entry.getValue() );
				result = jsonClient.invoke( Method.delete, null, "serviceSubscription", String.valueOf( entry.getValue() ), null );
				if ( getLogger().isDebugEnabled() )
				{
				    getLogger().debug( result.toString() );
				}
			}
		}

		// добавляем те, что активны в биллинге, но в текущих - нет
		for( Long serviceId : servicesToAdd )
		{
			if ( !currentServiceIds.containsKey( serviceId ) )
			{
			    getLogger().debug( "add serviceSubscription: " + serviceId );

				JSONObject serviceSubscription = new JSONObject()
				    .put( "abonent_account_id", abonentAccountId )
				    .put( "tariff_id", tariffDefault )
				    .put( "service_id", serviceId );

				result = jsonClient.invoke( Method.post, null, "serviceSubscription", null, serviceSubscription );
				if ( getLogger().isDebugEnabled() )
				{
				    getLogger().debug( result.toString() );
				}
			}
		}

		return null;
	}

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

		final String deviceAccountIdString = e.getTvAccount().getDeviceAccountId();
		final long[] deviceAccountId = parseDeviceAccountId( deviceAccountIdString );
		final long abonentAccountId = deviceAccountId[1];

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

		final Set<Long> servicesToRemove = new HashSet<Long>();
		final Set<Long> servicesToAdd = new HashSet<Long>();

		if ( serviceMode )
		{
			for( ServiceSpec serviceSpec : e.getServiceSpecSetToRemove() )
			{
				servicesToRemove.add( Utils.parseLong( serviceSpec.getIdentifier().trim() ) );
			}

			for( ServiceSpec serviceSpec : e.getServiceSpecSetToAdd() )
			{
				servicesToAdd.add( Utils.parseLong( serviceSpec.getIdentifier().trim() ) );
			}
		}
		else
		{
			for( ProductSpec productSpec : e.getProductSpecSetToRemove() )
			{
				servicesToRemove.add( Utils.parseLong( productSpec.getIdentifier().trim() ) );
			}

			for( ProductSpec productSpec : e.getDeviceOptionProductSpecSetToDisable() )
			{
				servicesToRemove.add( Utils.parseLong( productSpec.getIdentifier().trim() ) );
			}

			for( ProductSpec productSpec : e.getProductSpecSetToAdd() )
			{
				servicesToAdd.add( Utils.parseLong( productSpec.getIdentifier().trim() ) );
			}

			for( ProductSpec productSpec : e.getDeviceOptionProductSpecSetToEnable() )
			{
				servicesToAdd.add( Utils.parseLong( productSpec.getIdentifier().trim() ) );
			}
		}

		servicesToRemove.remove( 0L );
		servicesToAdd.remove( 0L );

		if ( servicesToRemove.size() > 0 )
		{
			return productsModifySyncFull( e, ctx, abonentAccountId );
		}

		for( Long serviceId : servicesToAdd )
		{
		    getLogger().debug( "add serviceSubscription: " + serviceId );

			JSONObject serviceSubscription = new JSONObject()
			    .put( "abonent_account_id", abonentAccountId )
			    .put( "tariff_id", tariffDefault )
			    .put( "service_id", serviceId );

			JSONObject result = jsonClient.invoke( Method.post, null, "serviceSubscription", null, serviceSubscription );
			if ( getLogger().isDebugEnabled() )
			{
			    getLogger().debug( result.toString() );
			}
		}

		return null;
	}

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

		long[] deviceAccountId = parseDeviceAccountId( e.getOldTvAccount().getDeviceAccountId() );
		long abonentAccountId = deviceAccountId[1];

		accountStateModify0( e, abonentAccountId );

		if ( e.isOptionsModified() )
		{
			accountOptionsModify0( e, ctx, abonentAccountId );
		}

		return null;
	}

	@Override
	public Object accountOptionsModify( AbstractOrderEvent e, ServerContext ctx )
		throws Exception
	{
		long[] deviceAccountId = parseDeviceAccountId( e.getTvAccountRuntime().getTvAccount().getDeviceAccountId() );
		if ( deviceAccountId == null )
		{
			return null;
		}

		long abonentAccountId = deviceAccountId[1];

		accountOptionsModify0( e, ctx, abonentAccountId );

		return null;
	}

	/**
	 * Обработка изменения опций TV.
	 * @param e
	 * @param ctx
	 * @param abonentAccountId
	 * @return
	 * @throws Exception
	 */
	private Object accountOptionsModify0( final AbstractOrderEvent e, final ServerContext ctx, final long abonentAccountId )
		throws Exception
	{
	    getLogger().debug( "accountOptionsModify0" );

		productsModifySyncFull( e, ctx, abonentAccountId );

		return null;
	}
}
