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

import static ru.bitel.common.PatternStringGenerator.insertPatternPart;

import java.net.InetSocketAddress;
import java.net.URI;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

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

import jakarta.xml.ws.BindingProvider;
import ru.bitel.bgbilling.kernel.container.managed.ServerContext;
import ru.bitel.bgbilling.kernel.module.common.bean.BGModule;
import ru.bitel.bgbilling.kernel.module.server.ModuleCache;
import ru.bitel.bgbilling.kernel.script.server.dev.GlobalScriptBase;
import ru.bitel.bgbilling.modules.tv.common.bean.TvAccount;
import ru.bitel.bgbilling.modules.tv.common.bean.TvAccountSpec;
import ru.bitel.bgbilling.modules.tv.common.bean.TvDevice;
import ru.bitel.bgbilling.modules.tv.common.bean.TvDeviceMap;
import ru.bitel.bgbilling.modules.tv.common.event.TvAccountModifiedEvent;
import ru.bitel.bgbilling.modules.tv.common.event.access.TvAccountDeviceStateAndOptionsModifiedEvent;
import ru.bitel.bgbilling.modules.tv.common.service.TvAccountService;
import ru.bitel.bgbilling.modules.tv.common.service.TvDeviceService;
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.SetTopBox;
import ru.bitel.bgbilling.modules.tv.server.TvUtils;
import ru.bitel.bgbilling.modules.tv.server.bean.TvAccountDao;
import ru.bitel.bgbilling.modules.tv.server.bean.enums.ModuleSetupKeys;
import ru.bitel.bgbilling.modules.tv.server.integration.bcc.telecomtv.ws.subscriber.InvalidParameterException_Exception;
import ru.bitel.bgbilling.modules.tv.server.runtime.TvAccountSpecRuntime;
import ru.bitel.bgbilling.modules.tv.server.runtime.TvAccountSpecRuntimeMap;
import ru.bitel.bgbilling.server.util.ModuleSetup;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.Utils;
import ru.bitel.common.sql.ConnectionSet;

public class TveTerminalSynchronizingTask
    extends GlobalScriptBase
{
	private static final Logger logger = LogManager.getLogger();

	@Override
	public void execute( Setup setup, ConnectionSet connectionSet )
	    throws Exception
	{
		final List<BGModule> moduleList = ModuleCache.getInstance().getModulesList( "tv" );

		for ( BGModule module : moduleList )
		{
			processModule( setup, connectionSet, module.getId() );
		}
	}

	private void processModule( Setup setup, ConnectionSet connectionSet, int moduleId )
	    throws Exception
	{
		logger.info( "processModule " + moduleId );
		print( "processModule " + moduleId );

		final ServerContext context = ServerContext.get();
		final ModuleSetup moduleSetup = setup.getModuleSetup( moduleId );

		final TvDeviceMap tvDeviceMap = TvDeviceMap.getInstance( moduleId );
		final TvDeviceService tvDeviceService = context.getService( TvDeviceService.class, moduleId );
		final TvAccountService tvAccountService = context.getService( TvAccountService.class, moduleId );

		final Map<String, TvAccountSpecRuntime> terminalAccountSpecMap = new HashMap<>();

		final TvAccountSpecRuntimeMap tvAccountSpecRuntimeMap = new TvAccountSpecRuntimeMap( connectionSet.getConnection(), moduleId );

		final List<TvAccountSpec> tvAccountSpecList = tvAccountService.tvAccountSpecList();
		for ( TvAccountSpec tvAccountSpec : tvAccountSpecList )
		{
			TvAccountSpecRuntime tvAccountSpecRuntime = tvAccountSpecRuntimeMap.get( tvAccountSpec.getId() );

			String terminalType = tvAccountSpecRuntime.config.get( "terminal.type", null );

			if ( Utils.notBlankString( terminalType ) )
			{
				terminalAccountSpecMap.put( terminalType, tvAccountSpecRuntime );
			}
		}

		final String val = Optional.ofNullable( moduleSetup.get( ModuleSetupKeys.OM_TVE_DEVICE_TYPE_IDS ) )
		    .orElse( moduleSetup.get( ModuleSetupKeys.OM_DEVICE_TYPE_IDS ) );
		final List<Integer> deviceTypeIds = Utils.toIntegerList( val );
		for ( Integer deviceTypeId : deviceTypeIds )
		{
			for ( TvDevice tvDevice : tvDeviceService.deviceList( deviceTypeId ) )
			{
				processDevice( setup, context, connectionSet, moduleId, tvDeviceMap, tvDevice, terminalAccountSpecMap, tvAccountService );
			}
		}
	}

	private void processDevice( Setup setup, ServerContext context, ConnectionSet connectionSet, int moduleId, TvDeviceMap tvDeviceMap,
	                            TvDevice tvDevice, Map<String, TvAccountSpecRuntime> terminalAccountSpecMap, TvAccountService tvAccountService )
	    throws Exception
	{
		logger.info( "processDevice " + tvDevice.getId() );
		print( "processDevice " + tvDevice.getId() );

		// final TvDeviceMapItem tvDeviceMapItem = tvDeviceMap.get( tvDevice.getId() );
		// final ParameterMap config = tvDeviceMapItem.getConfig();

		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;
		}

		CustomerManagementServiceWSService customerManagementServiceWSService = new CustomerManagementServiceWSService( URI.create( "http://" + host + ":" + port + "/tve-billing-ws/CustomerManagementServiceWS?wsdl" ).toURL() );
		CustomerManagementServiceWS customerManagementService = customerManagementServiceWSService.getCustomerManagementServiceWSPort();
		Map<String, Object> requestContext = ((BindingProvider)customerManagementService).getRequestContext();
		requestContext.put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://" + host + ":" + port + "/tve-billing-ws/CustomerManagementServiceWS" );

		TvAccountDao tvAccountDao = new TvAccountDao( connectionSet.getConnection(), moduleId );

		processDevice( context, connectionSet, moduleId, tvDeviceMap, tvDevice, tvAccountDao,
		               terminalAccountSpecMap, tvAccountService, customerManagementService );

	}

	private void processDevice( ServerContext context, ConnectionSet connectionSet, int moduleId, TvDeviceMap tvDeviceMap, TvDevice tvDevice, TvAccountDao tvAccountDao,
	                            Map<String, TvAccountSpecRuntime> terminalAccountSpecMap,
	                            TvAccountService tvAccountService, CustomerManagementServiceWS customerManagementService )
	    throws Exception
	{
		Date now = new Date();

		List<TvAccount> tvAccountList = tvAccountService.tvAccountList( tvDevice.getId(), null, now, now );
		for ( TvAccount tvAccount : tvAccountList )
		{
			if ( tvAccount.getParentId() > 0 )
			{
				continue;
			}

			final String accountNumber = tvAccount.getDeviceAccountId();
			if ( Utils.isBlankString( accountNumber ) )
			{
				logger.info( "AccountNumber not specified for tvAccount:" + tvAccount.getId() );
				print( "AccountNumber not specified for tvAccount:" + tvAccount.getId() );
				continue;
			}

			try
			{
				processAccount( context, connectionSet, moduleId, tvDeviceMap, tvDevice, tvAccountDao, terminalAccountSpecMap,
				                tvAccountService, customerManagementService, accountNumber, tvAccount );
			}
			catch( InvalidParameterException_Exception ex )
			{
				logger.info( "AccountNumber not found with id:" + accountNumber + " for tvAccount:" + tvAccount.getId() + ". " + ex.getMessage() );
				print( "AccountNumber not found with id:" + accountNumber + " for tvAccount:" + tvAccount.getId() + ". " + ex.getMessage() );
			}

			context.commit();
		}
	}

	private void processAccount( ServerContext context, ConnectionSet connectionSet, int moduleId, TvDeviceMap tvDeviceMap, TvDevice tvDevice, TvAccountDao tvAccountDao,
	                             Map<String, TvAccountSpecRuntime> terminalAccountSpecMap,
	                             TvAccountService tvAccountService, CustomerManagementServiceWS customerManagementService, String accountNumber, TvAccount parentTvAccount )
	    throws Exception
	{
		List<TvAccount> children = tvAccountDao.listChildren( parentTvAccount.getId() );

		List<SetTopBox> setTopBoxList = customerManagementService.getSetTopBoxes( accountNumber );
		TERMINAL: for ( SetTopBox terminal : setTopBoxList )
		{
			if ( children != null )
			{
				for ( TvAccount child : children )
				{
					if ( child.getDeviceAccountId().equals( terminal.getId() ) )
					{
						continue TERMINAL;
					}

					if ( Utils.isBlankString( child.getDeviceAccountId() )
					    && terminal.getMac().equalsIgnoreCase( TvUtils.macAddressToString( child.getMacAddressListBytes() ) )
					    && terminal.getSerrialNumber().equalsIgnoreCase( child.getIdentifier() ) )
					{
						logger.info( "Found not linked by deviceAccountId terminal" );
						print( "Found not linked by deviceAccountId terminal" );

						String deviceAccountId = String.valueOf( terminal.getId() );

						tvAccountDao.updateDeviceStateAndOptions( child.getId(), deviceAccountId, TvAccount.STATE_ENABLE, child.getDeviceOptionIds(), child.getAccessCode() );
                        context.publishAfterCommit( new TvAccountDeviceStateAndOptionsModifiedEvent( moduleId, parentTvAccount.getContractId(),
                            child.getDeviceId(), child.getId(), deviceAccountId, TvAccount.STATE_ENABLE, null ) );

						continue TERMINAL;
					}
				}
			}

			TvAccountSpecRuntime tvAccountSpecRuntime = terminalAccountSpecMap.get( terminal.getType() );
			if ( tvAccountSpecRuntime == null )
			{
				logger.error( "Found not linked terminal, but tvAccountSpec not found for terminalType=" + terminal.getType() );
				print( "Found not linked terminal, but tvAccountSpec not found for terminalType=" + terminal.getType() );

				continue;
			}

			logger.info( "Found new terminal in MW" );
			print( "Found new terminal in MW" );

			TvAccount tvAccount = new TvAccount();
			tvAccount.setContractId( parentTvAccount.getContractId() );
			tvAccount.setParentId( parentTvAccount.getId() );
			tvAccount.setSpecId( tvAccountSpecRuntime.tvAccountSpec.getId() );

			tvAccount.setDeviceId( parentTvAccount.getDeviceId() );

			tvAccount.setDateFrom( parentTvAccount.getDateFrom() );
			tvAccount.setDateTo( parentTvAccount.getDateTo() );

			tvAccount.setMacAddressList( Collections.singletonList( TvUtils.parseMacAddress( terminal.getMac() ) ) );

			tvAccount.setIdentifier( terminal.getSerrialNumber() );

			tvAccount.setLogin( null );
			tvAccount.setPassword( null );
			tvAccount.setStatus( TvAccount.STATUS_ACTIVE );

			tvAccount.setComment( "" );

			tvAccount.setDeviceState( TvAccount.STATE_ENABLE );
			tvAccount.setDeviceAccountId( String.valueOf( terminal.getId() ) );

			String titlePattern = tvAccountSpecRuntime.config.get( "title.pattern", "Укажите параметр title.pattern в конфигурации типа сервиса!" );
			titlePattern = insertPatternPart( titlePattern, "deviceIdentifier", tvDevice.getIdentifier() );
			titlePattern = insertPatternPart( titlePattern, "deviceTitle", tvDevice.getTitle() );
			titlePattern = insertPatternPart( titlePattern, "login", tvAccount.getLogin() );
			titlePattern = insertPatternPart( titlePattern, "id", String.valueOf( tvAccount.getId() ) );
			titlePattern = insertPatternPart( titlePattern, "identifier", tvAccount.getIdentifier() );

			StringBuilder macString = new StringBuilder();

			if ( tvAccount.getMacAddressList() != null && tvAccount.getMacAddressList().size() > 0 )
			{
				for ( byte[] mac : tvAccount.getMacAddressList() )
				{
					macString.append( Utils.bytesToString( mac, true, null ) );
					macString.append( ", " );
				}

				macString.setLength( macString.length() - 2 );
			}

			titlePattern = insertPatternPart( titlePattern, "macAddress", macString.toString() );

			tvAccount.setTitle( titlePattern );

			logger.info( titlePattern );
			print( titlePattern );

			tvAccountDao.update( tvAccount );
			tvAccountDao.updateDeviceStateAndOptions( tvAccount.getId(), tvAccount.getDeviceAccountId(), tvAccount.getDeviceState(), null, 0 );
			context.publishAfterCommit( new TvAccountModifiedEvent( moduleId, tvAccount.getContractId(), 0, null, tvAccount ) );

			logger.info( "Link terminal:" + terminal.getId() + " with tvAccount:" + tvAccount.getId() );
			print( "Link terminal:" + terminal.getId() + " with tvAccount:" + tvAccount.getId() );
		}
	}
}
