package ru.bitel.bgbilling.modules.tv.dyn.cti.tve;

import java.math.BigDecimal;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URL;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

import jakarta.annotation.Resource;
import jakarta.xml.ws.BindingProvider;
import jakarta.xml.ws.handler.Handler;
import jakarta.xml.ws.handler.HandlerResolver;
import jakarta.xml.ws.handler.PortInfo;
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.ProductEntry;
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.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.contract.balance.server.util.BalanceUtils;
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.dyn.TvDynUtils;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.TveUtils;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.customer.BillingException_Exception;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.customer.CustomerAccount;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.customer.CustomerManagementServiceWS;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.customer.CustomerManagementServiceWSService;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.customer.Individual;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.customer.Organization;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.customer.SetTopBox;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.customer.StbStatus;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.Service;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.ServiceManagementServiceWS;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.ServiceManagementServiceWSService;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.ServiceSpecification;
import ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.TimeFrame;
import ru.bitel.bgbilling.modules.tv.server.TvUtils;
import ru.bitel.bgbilling.modules.tv.server.runtime.TvAccountSpecRuntime;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.TimeUtils;
import ru.bitel.common.Utils;
import ru.bitel.common.logging.LoggingSOAPHandlerClient;
import ru.bitel.oss.kernel.entity.common.bean.EntityAttrList;
import ru.bitel.oss.systems.inventory.product.common.bean.Product;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductOffering;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductOfferingActivationMode;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductSpec;
import ru.bitel.oss.systems.order.product.common.service.ProductOrderService;

public class TveOrderManager
	extends OrderManagerAdapter
	implements OrderManager
{
	@Resource(name = "access")
	private TvAccess access;
	
	private String host;
	private int port;

	static class TerminalSpec
	{
		public final int tvAccountSpecId;
		public final String type;

		public TerminalSpec( int tvAccountSpecId, String type )
		{
			this.tvAccountSpecId = tvAccountSpecId;
			this.type = type;
		}
	}

	private int moduleId;

	private Map<Integer, TerminalSpec> terminalSpecMap;

	private CustomerManagementServiceWS customerManagementService;
	private ServiceManagementServiceWS serviceManagementService;

	private ProductOrderService productOrderService;

    private ContractDao contractDao;
    private BalanceUtils balanceUtils;

	private int customerFirstNamePid;
	private int customerMiddleNamePid;
	private int customerLastNamePid;
	private int customerGenderPid;
	private int customerBirthDatePid;
	private int customerCompanyPid;

	private HandlerResolver newHandlerResolver( final jakarta.xml.ws.Service webService )
	{
		return new HandlerResolver()
		{
			@SuppressWarnings({ "rawtypes" })
			@Override
			public List<Handler> getHandlerChain( PortInfo portInfo )
			{
				List<Handler> result = new ArrayList<>();
				result.add( new LoggingSOAPHandlerClient( webService ) );
				return result;
			}
		};
	}

	@Override
	public Object init( ServerContext ctx, int moduleId, TvDevice tvDevice, TvDeviceType tvDeviceType, ParameterMap config )
		throws Exception
	{
		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 = "127.0.0.1";
		}

		if ( port <= 0 )
		{
			port = 8080;
		}
		
		this.host = host;
		this.port = port;

		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 );
		customerGenderPid = config.getInt( "customer.gender.pid", 0 );
		customerBirthDatePid = config.getInt( "customer.birthDate.pid", 0 );
		customerCompanyPid = config.getInt( "customer.company.pid", customerNamePid );

		terminalSpecMap = new HashMap<Integer, TerminalSpec>();

		for ( TvAccountSpecRuntime tvAccountSpecRuntime : access.tvAccountSpecRuntimeMap.list() )
		{
			String type = tvAccountSpecRuntime.config.get( "terminal.type", null );
			if ( Utils.isBlankString( type ) )
			{
				continue;
			}

			TerminalSpec terminalSpec = new TerminalSpec( tvAccountSpecRuntime.tvAccountSpec.getId(), type );
			terminalSpecMap.put( tvAccountSpecRuntime.tvAccountSpec.getId(), terminalSpec );
		}

		return null;
	}

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

	@Override
	public Object connect( ServerContext ctx )
		throws Exception
	{
		final Connection con = ctx.getConnection();

        balanceUtils = new BalanceUtils( con );
		contractDao = new ContractDao( con, User.USER_SERVER );
		productOrderService = ctx.getService( ProductOrderService.class, 0 );
		
		if ( customerManagementService == null || serviceManagementService == null )
		{
			String urlString = "http://" + host + ":" + port;
			URL url = URI.create( urlString ).toURL();

			CustomerManagementServiceWSService customerManagementServiceWSService = new CustomerManagementServiceWSService( new URL( url, "tve-billing-ws/CustomerManagementServiceWS?wsdl" ) );
			customerManagementServiceWSService.setHandlerResolver( newHandlerResolver( customerManagementServiceWSService ) );
			customerManagementService = customerManagementServiceWSService.getCustomerManagementServiceWSPort();
			Map<String, Object> requestContext = ((BindingProvider)customerManagementService).getRequestContext();
			requestContext.put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, urlString + "/tve-billing-ws/CustomerManagementServiceWS" );

			ServiceManagementServiceWSService serviceManagementServiceWSService = new ServiceManagementServiceWSService( new URL( url, "tve-billing-ws/ServiceManagementServiceWS?wsdl" ) );
			serviceManagementServiceWSService.setHandlerResolver( newHandlerResolver( serviceManagementServiceWSService ) );
			serviceManagementService = serviceManagementServiceWSService.getServiceManagementServiceWSPort();
			requestContext = ((BindingProvider)serviceManagementService).getRequestContext();
			requestContext.put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, urlString + "/tve-billing-ws/ServiceManagementServiceWS" );
		}

		return null;
	}

	@Override
	public Object disconnect( ServerContext ctx )
		throws Exception
	{
		return null;
	}

	/**
	 * Проверка и создание customer
	 * @param contractId
	 * @return
	 * @throws BillingException_Exception
	 */
	private String checkCustomer( final Contract contract )
		throws BillingException_Exception, BGException
	{
		final String customerId = String.valueOf( contract.getId() );

		if ( contract.getFace() == ContractFace.URIC )
		{
			final String name = TvDynUtils.getName( contractDao, contract, customerCompanyPid );

			Organization customer = new Organization();
			customer.setId( customerId );
			customer.setName( Utils.maskBlank( name, Utils.maskBlank( contract.getTitle(), "undef" ) ) );

			int result = customerManagementService.modifyOrganization( customer );
			getLogger().info( "modifyOrganization: " + result );
			result = customerManagementService.addOrganization( customer );
			getLogger().info( "addOrganization: " + result );
		}
		else
		{
			final String[] name = TvDynUtils.getName( contractDao, contract, customerLastNamePid, customerFirstNamePid, customerMiddleNamePid );

			final Individual customer = new Individual();
			customer.setId( customerId );
			customer.setName( Utils.maskBlank( name[0], Utils.maskBlank( contract.getTitle(), "undef" ) ) );
			customer.setLastName( Utils.maskBlank( name[1], Utils.maskBlank( contract.getTitle(), "undef" ) ) );
			customer.setFirstName( Utils.maskBlank( name[2], "undef" ) );
			customer.setMiddleName( Utils.maskBlank( name[3], "undef" ) );

			customer.setGender( 0 );

			if ( customerGenderPid > 0 )
			{
				customer.setGender( contractDao.getContractParameterList( contract.getId(), customerGenderPid ).map( EntityAttrList::getValue ).orElse( 0 ) );
			}

			if ( customerBirthDatePid > 0 )
			{
				// customer.setBirthDate( contractParameterManager.getListParam( contractId, customerPidGender ) );
			}

			int result = customerManagementService.modifyIndividual( customer );
			getLogger().info( "modifyIndividual: " + result );

			if ( result == 1 )
			{
				result = customerManagementService.addIndividual( customer );
				getLogger().info( "addIndividual: " + result );
			}
		}

		return customerId;
	}

	private static XMLGregorianCalendar wrap( Date date )
		throws Exception
	{
		if ( date == null )
		{
			return null;
		}

		GregorianCalendar calendar = new GregorianCalendar();
		calendar.setTime( date );
		return DatatypeFactory.newInstance().newXMLGregorianCalendar( calendar );
	}

	@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" );

		final String accountNumber = accountModify0( e, ctx );
		final List<SetTopBox> deviceSetTopBoxList = customerManagementService.getSetTopBoxes( accountNumber );

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

		productsModifySyncFull( e.getNewTvAccount(), accountNumber, e, ctx );

		return null;
	}

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

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

		final Contract contract = contractDao.get( contractId );

		final String customerId = checkCustomer( contract );

		CustomerAccount customerAccount = new CustomerAccount();
		customerAccount.setCustomerId( customerId );
		customerAccount.setDateBegin( wrap( tvAccount.getDateFrom() ) );
		customerAccount.setAccountNumber( tvAccount.getLogin() );
		customerAccount.setPin( tvAccount.getPassword() );
		customerAccount.setAccountStatus( e.getNewState() == TvAccount.STATE_ENABLE ? 1 : 0 );
		customerAccount.setActivationAttempts( 10 );
		customerAccount.setStbActivationEnabled( true );

		BigDecimal balance = balanceUtils.getBalanceOut( new Date(), contract.getId() );
		balanceUtils.close();

		customerAccount.setBalance( balance.doubleValue() );
		customerAccount.setCreditLimit( contract.getBalanceLimit().doubleValue() );

		if ( e.getOldTvAccount() == null || Utils.isBlankString( e.getOldTvAccount().getDeviceAccountId() ) )
		{
		    getLogger().debug( "e.getOldTvAccount() = " + Optional.ofNullable( e.getOldTvAccount() ).map( a -> a.toString() ).orElse( "null" ) );

			accountAdd( e, customerAccount );
		}
		else if ( !tvAccount.getLogin().equals( e.getOldTvAccount().getDeviceAccountId() ) )
		{
			customerAccount.setAccountNumber( e.getOldTvAccount().getLogin() );
			accountRemove( e, customerAccount );

			customerAccount.setAccountNumber( tvAccount.getLogin() );
			accountAdd( e, customerAccount );
		}
		else
		{
			accountModify( e, customerAccount );
		}

		return customerAccount.getAccountNumber();
	}

	private void accountAdd( final AccountOrderEvent e, final CustomerAccount customerAccount )
	{
		int result = customerManagementService.addCustomerAccountN( customerAccount );
		getLogger().debug( "addCustomerAccountN: " + result );

		if ( result == 401 )
		{
			result = customerManagementService.modifyCustomerAccountN( customerAccount );
			getLogger().debug( "modifyCustomerAccountN: " + result );
		}

		e.getEntry().setDeviceAccountId( customerAccount.getAccountNumber() );
		
		final TvAccount tvAccount = e.getNewTvAccount();
		final String accountNumber = tvAccount.getLogin();

		customerManagementService.changeCustomerAccountStatusN( accountNumber, e.getNewState() == TvAccount.STATE_ENABLE ? 1 : 0, "", e.getNewState() == TvAccount.STATE_ENABLE );
	}

	private void accountModify( final AccountOrderEvent e, final CustomerAccount customerAccount )
	{
		int result = customerManagementService.modifyCustomerAccountN( customerAccount );
		getLogger().debug( "modifyCustomerAccountN: " + result );

		if ( result == 2 )
		{
			result = customerManagementService.addCustomerAccountN( customerAccount );
			getLogger().debug( "addCustomerAccountN: " + result );

			e.getEntry().setDeviceAccountId( customerAccount.getAccountNumber() );
		}

		e.getEntry().setDeviceAccountId( customerAccount.getAccountNumber() );
		
		final TvAccount tvAccount = e.getNewTvAccount();
		final String accountNumber = tvAccount.getLogin();

		customerManagementService.changeCustomerAccountStatusN( accountNumber, e.getNewState() == TvAccount.STATE_ENABLE ? 1 : 0, "", e.getNewState() == TvAccount.STATE_ENABLE );
	}

	private void accountRemove( final AccountOrderEvent e, final CustomerAccount customerAccount )
	{
		int result = customerManagementService.removeCustomerAccount( customerAccount );
		getLogger().debug( "removeCustomerAccount: " + result );
	}

	private SetTopBox getSetTopBox( final List<SetTopBox> deviceSetTopBoxList, final String macAddress )
	{
		for ( SetTopBox deviceSetTopBox : deviceSetTopBoxList )
		{
			if ( macAddress.equals( deviceSetTopBox.getMac() ) )
			{
				return deviceSetTopBox;
			}
		}

		return null;
	}

	private void terminalAdd( final String accountNumber, final AccountEntry entry, final TvAccount setTopBox )
		throws BillingException_Exception
	{
		int result = customerManagementService.addSetTopBoxToAccount( accountNumber, toSetTopBox( setTopBox ) );
		getLogger().debug( "addSetTopBoxToAccount: " + result );

		if ( result == 0 )
		{
			final String macAddress = TvUtils.macAddressToString( setTopBox.getMacAddressListBytes() );

			final List<SetTopBox> deviceSetTopBoxList = customerManagementService.getSetTopBoxes( accountNumber );
			for ( SetTopBox deviceSetTopBox : deviceSetTopBoxList )
			{
				if ( deviceSetTopBox.getMac().equals( macAddress ) )
				{
					entry.setDeviceAccountId( String.valueOf( deviceSetTopBox.getId() ) );
					break;
				}
			}
		}
	}

	private void terminalRemove( final String accountNumber, final AccountEntry entry, final List<SetTopBox> deviceSetTopBoxList )
		throws BillingException_Exception
	{
		final String macAddress = TvUtils.macAddressToString( entry.getOldTvAccount().getMacAddressListBytes() );
		SetTopBox setTopBox = getSetTopBox( deviceSetTopBoxList, macAddress );
		if ( setTopBox == null )
		{
			setTopBox = toSetTopBox( entry.getOldTvAccount() );
		}

		int result = customerManagementService.removeSetTopBoxFromAccount( accountNumber, setTopBox );
		getLogger().debug( "removeSetTopBoxFromAccount: " + result );
	}

	private void terminalModify( final String accountNumber, final AccountEntry entry, final ServerContext ctx, final List<SetTopBox> deviceSetTopBoxList )
		throws Exception
	{
	    getLogger().info( "terminalModify" );

		if ( entry.getNewState() == TvAccount.STATE_DELETED )
		{
			terminalRemove( accountNumber, entry, deviceSetTopBoxList );

			return;
		}

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

		if ( entry.getOldTvAccount() == null /*|| Utils.isBlankString( entry.getOldTvAccount().getDeviceAccountId() )*/)
		{
			terminalAdd( accountNumber, entry, tvAccount );
		}
		else
		{
			terminalRemove( accountNumber, entry, deviceSetTopBoxList );

			terminalAdd( accountNumber, entry, tvAccount );
		}
	}

	private SetTopBox toSetTopBox( final TvAccount setTopBox )
	{
		TerminalSpec terminalSpec = terminalSpecMap.get( setTopBox.getSpecId() );

		SetTopBox result = new SetTopBox();
		result.setId( String.valueOf( setTopBox.getId() ) );
		result.setMac( TvUtils.macAddressToString( setTopBox.getMacAddressListBytes() ) );// "E8:BE:81:8A:3B:D8"
		result.setSerrialNumber( setTopBox.getIdentifier() );//"611067030648"
		result.setType( terminalSpec != null ? terminalSpec.type : "STB" );
		result.setStatus( StbStatus.ACTIVE );

		return result;
	}

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

		final int contractId = e.getContractId();
		final String customerId = String.valueOf( contractId );

		final TvAccount tvAccount = e.getOldTvAccount();
		final String accountNumber = tvAccount.getLogin();

		for ( AccountEntry child : e.getChildrenEntryList() )
		{
			final TvAccount terminal = child.getOldTvAccount();
			if ( terminal == null )
			{
				continue;
			}

			int result = customerManagementService.removeSetTopBoxFromAccount( accountNumber, toSetTopBox( terminal ) );
			getLogger().debug( "removeSetTopBoxFromAccount: " + result );
		}

		CustomerAccount customerAccount = new CustomerAccount();
		customerAccount.setCustomerId( customerId );
		customerAccount.setAccountNumber( accountNumber );

		accountRemove( e, customerAccount );

		return null;
	}

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

		final TvAccount tvAccount = e.getNewTvAccount();
		final String accountNumber = tvAccount.getLogin();

		customerManagementService.changeCustomerAccountStatusN( accountNumber, e.getNewState() == TvAccount.STATE_ENABLE ? 1 : 0, "", e.getNewState() == TvAccount.STATE_ENABLE );

		return null;
	}

	private void setRentTime( TvAccount tvAccount, Product product, Service tveService )
		throws Exception
	{
		ProductOffering productOffering = productOrderService.productOfferingGet( moduleId, tvAccount.getContractId(), tvAccount.getId(),
																				  product.getProductSpecId(), product.getActivationModeId(),
																				  new Date(), false, false );

		ProductOfferingActivationMode offeringActivationMode = productOffering.getActivationModeList().get( 0 );
//		ProductSpecActivationMode activationMode = offeringActivationMode.getProductSpecActivationMode();

		ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.Service.AdditionalParameters additionalParameters =
			new ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.Service.AdditionalParameters();

		if ( productOffering != null )
		{
//			int rentTime;
			if ( BigDecimal.ZERO.compareTo( offeringActivationMode.getPrice() ) != 0 && BigDecimal.ZERO.compareTo( offeringActivationMode.getActivationPrice() ) == 0 )
			{
//				rentTime = (int)TvUtils.periodAmountToUnit( offeringActivationMode.getPeriodMode(), offeringActivationMode.getPeriodAmount(), TimeUnit.HOURS );
			}
			else
			{
//				rentTime = (int)TvUtils.periodAmountToUnit( activationMode.getPeriodMode(), activationMode.getPeriodAmount(), TimeUnit.HOURS );
			}

			tveService.setPrice( productOffering.getPrice().doubleValue() );

			ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.Service.AdditionalParameters.Entry e =
				new ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.Service.AdditionalParameters.Entry();
			e.setKey( "rentTime" );
			e.setValue( (short)60 );

			additionalParameters.getEntry().add( e );

			e = new ru.bitel.bgbilling.modules.tv.dyn.cti.tve.ws.service.Service.AdditionalParameters.Entry();
			e.setKey( "rentalPeriod" );
			e.setValue( (long)60 * 60 * 1000 );

			additionalParameters.getEntry().add( e );
		}

		tveService.setAdditionalParameters( additionalParameters );
	}

	private void addTveService( final TvAccount tvAccount, String accountNumber, ProductSpec productSpec, Product product )
		throws Exception
	{
	    getLogger().info( "addTveService" );

		Service tveService = new Service();
		tveService.setId( (long)product.getId() );
		tveService.setName( productSpec.getTitle() );
		tveService.setPeriodical( true );

		TimeFrame timeFrame = new TimeFrame();
		
		getLogger().info( "Product time: " + TimeUtils.format( product.getTimeFrom(), "dd.MM.yyyy HH:mm:ss" )
					 + "-" + TimeUtils.format( product.getTimeTo(), "dd.MM.yyyy HH:mm:ss" ) );
		getLogger().info( "Subscription time: " + TimeUtils.format( product.getSubscriptionTimeFrom(), "dd.MM.yyyy HH:mm:ss" )
					 + "-" + TimeUtils.format( product.getSubscriptionTimeTo(), "dd.MM.yyyy HH:mm:ss" ) );

		if ( product.getSubscriptionTimeFrom() != null )
		{
			GregorianCalendar timeFrom = new GregorianCalendar();
			timeFrom.setTime( product.getSubscriptionTimeFrom() );
			timeFrom.add( Calendar.MINUTE, -5 );

			timeFrame.setStartTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( timeFrom ) );
		}
		else
		{
			timeFrame.setStartTime( TveUtils.toXmlCalendar( product.getTimeFrom() ) );
		}

		GregorianCalendar timeTo = new GregorianCalendar();
		timeTo.setTime( product.getSubscriptionTimeTo() );
		timeTo.add( Calendar.MINUTE, 20 );
		
		//timeTo.setTime( product.getTimeFrom() );
		//timeTo.add( Calendar.HOUR_OF_DAY, 1 );
		//timeTo.add( Calendar.MINUTE, 20 );

		timeFrame.setEndTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( timeTo ) );

		tveService.setValidFor( timeFrame );

		setRentTime( tvAccount, product, tveService );

		ServiceSpecification serviceSpecification = new ServiceSpecification();
		serviceSpecification.setId( Utils.parseLong( productSpec.getIdentifier() ) );
		tveService.setServiceSpecification( serviceSpecification );

		int result = serviceManagementService.addService( accountNumber, tveService );
		getLogger().debug( "addService: " + result );
	}

	private void removeTveService( String accountNumber, long tveServiceId )
	{
	    getLogger().info( "removeTveService: " + tveServiceId );

		int result = serviceManagementService.removeService( accountNumber, tveServiceId );
		getLogger().debug( "removeService: " + result );
	}

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

		final TvAccount tvAccount = e.getTvAccount();
		final String accountNumber = tvAccount.getLogin();

		if ( true )
		{
			productsModifySyncFull( tvAccount, accountNumber, e, ctx );
		}

		return null;

//		for ( ProductEntry productEntry : e.getProductEntryList() )
//		{
//			logger.debug( "productEntry" );
//
//			final int newState = productEntry.getNewState();
//			final int oldState = productEntry.getOldState();
//
//			if ( newState == oldState )
//			{
//				continue;
//			}
//
//			if ( newState == Product.STATE_ENABLED )
//			{
//				logger.info( "Add ctiService" );
//
//				final Product newProduct = productEntry.getNewProduct();
//				final ProductSpec productSpec = productEntry.getProductSpec();
//
//				addTveService( tvAccount, accountNumber, productSpec, newProduct );
//
//				newProduct.setDeviceProductId( String.valueOf( newProduct.getId() ) );
//			}
//			else if ( oldState == Product.STATE_ENABLED )
//			{
//				logger.info( "Remove ctiService" );
//
//				final Product oldProduct = productEntry.getOldProduct();
//
//				removeTveService( accountNumber, (long)oldProduct.getId() );
//			}
//		}

//		return null;
	}

	private void productsModifySyncFull( final TvAccount tvAccount, final String accountNumber, final AbstractOrderEvent e, final ServerContext ctx )
		throws Exception
	{
	    getLogger().debug( "productsModifyFullSync" );

		final List<ProductEntry> productEntryListToEnable = e.getFullProductEntryListToEnable();
		final List<Service> currentTveServiceList = serviceManagementService.getServices( accountNumber );

		final Map<Long, ProductEntry> tveServiceIdsToEnable = new HashMap<Long, ProductEntry>();
		for ( ProductEntry productEntry : productEntryListToEnable )
		{
		    getLogger().info( Optional.ofNullable( productEntry.getNewProduct() ).map( a -> a.toString() ).orElse( "" ) );
		    getLogger().info( Optional.ofNullable( productEntry.getNewState() ).map( a -> a.toString() ).orElse( "" )  );

			if ( productEntry.getNewProduct() != null )
			{
			    getLogger().info( Optional.ofNullable( productEntry.getNewProduct() ).map( a -> String.valueOf( a.getId() ) ).orElse( "" ) );
			    getLogger().info( "" + productEntry.getNewProduct().getDeviceState() );
			}

			tveServiceIdsToEnable.put( (long)productEntry.getNewProduct().getId(), productEntry );
		}

		final Set<Long> tveServiceIdsCurrent = new HashSet<Long>();
		for ( Service tveService : currentTveServiceList )
		{
			if ( tveService.getId() == null )
			{
				continue;
			}
			
			final ProductEntry pe = tveServiceIdsToEnable.get( tveService.getId() );

			if ( pe == null )
			{
				removeTveService( accountNumber, tveService.getId() );
				continue;
			}

			tveServiceIdsCurrent.add( tveService.getId() );

			final Product product = pe.getNewProduct();

			getLogger().info( "Product time: " + TimeUtils.format( product.getTimeFrom(), "dd.MM.yyyy HH:mm:ss" )
						 + "-" + TimeUtils.format( product.getTimeTo(), "dd.MM.yyyy HH:mm:ss" ) );
			getLogger().info( "Subscription time: " + TimeUtils.format( product.getSubscriptionTimeFrom(), "dd.MM.yyyy HH:mm:ss" )
						 + "-" + TimeUtils.format( product.getSubscriptionTimeTo(), "dd.MM.yyyy HH:mm:ss" ) );
			getLogger().info( "validForStartTime: " + TimeUtils.format( tveService.getValidFor().getStartTime().toGregorianCalendar(), "dd.MM.yyyy HH:mm:ss" ) );
			getLogger().info( "validForEndTime: " + TimeUtils.format( tveService.getValidFor().getEndTime().toGregorianCalendar(), "dd.MM.yyyy HH:mm:ss" ) );

			GregorianCalendar timeFrom = new GregorianCalendar();
			if ( product.getSubscriptionTimeFrom() != null )
			{
				timeFrom.setTime( product.getSubscriptionTimeFrom() );
				timeFrom.add( Calendar.MINUTE, -5 );
			}
			else
			{
				timeFrom.setTime( product.getTimeFrom() );
			}
			
			timeFrom.setTime( product.getTimeFrom() );

			final GregorianCalendar timeTo = new GregorianCalendar();
			timeTo.setTime( product.getSubscriptionTimeTo() );
			timeTo.add( Calendar.MINUTE, 20 );
			
			//timeTo.setTime( timeFrom.getTime() );
			//timeTo.add( Calendar.MINUTE, 60 * ((int)((product.getSubscriptionTimeTo().getTime() - timeFrom.getTimeInMillis()) / 1000 / 60)+1)+1 );

			getLogger().info( "timeTo: " + TimeUtils.format( timeTo, "dd.MM.yyyy HH:mm:ss" ) );

			if ( TimeUtils.compare( timeFrom, tveService.getValidFor().getStartTime().toGregorianCalendar(), Calendar.MINUTE ) == 0
				&& TimeUtils.compare( timeTo, tveService.getValidFor().getEndTime().toGregorianCalendar(), Calendar.MINUTE ) == 0 )
			{
				continue;
			}

			tveService.getValidFor().setStartTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( timeFrom ) );
			tveService.getValidFor().setEndTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( timeTo ) );
			
			setRentTime( tvAccount, product, tveService );

			getLogger().info( "modify tveService: " + tveService.getId() );

			int result = serviceManagementService.modifyService( accountNumber, tveService );
			getLogger().debug( "modifyService: " + result );
		}

		for ( ProductEntry productEntry : productEntryListToEnable )
		{
		    getLogger().info( "" + productEntry );

			final long tveServiceId = (long)productEntry.getNewProduct().getId();

			if ( tveServiceIdsCurrent.contains( tveServiceId ) )
			{
				continue;
			}

			final Product newProduct = productEntry.getNewProduct();
			final ProductSpec productSpec = productEntry.getProductSpec();

			addTveService( tvAccount, accountNumber, productSpec, newProduct );

			newProduct.setDeviceProductId( String.valueOf( newProduct.getId() ) );
		}
	}
}
