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

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
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.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.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.JsonClientException;
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;

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

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

	private int moduleId;

	private JsonClient jsonClient;

	private ContractDao contractDao;
	private ContractManager contractManager;

	private TvAccountDao tvAccountDao;

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

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

	private Set<Integer> alwaysEnableProductIds;
	private Set<Long> baseProductIdentifiers;

	public JSONObject invoke( final Method method, final String resource, final String id, final JSONObject obj )
		throws IOException, BGException, JSONException
	{
		return jsonClient.invoke( method, null, ("rest/" + resource) + "/format/json", id, obj );
	}

	public JSONArray invokeAndGetArray( final Method method, final String resource, final String id, final JSONObject obj )
		throws IOException, BGException, JSONException
	{
		return jsonClient.invokeAndGetArray( method, null, ("rest/" + resource) + "/format/json", id, obj );
	}

	public Object invokeAndGetObject( final Method method, final String resource, final String id, final JSONObject obj )
		throws IOException, BGException, JSONException
	{
		return jsonClient.invokeAndGetObject( method, null, ("rest/" + resource) + "/format/json", id, obj );
	}

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

		super.init( ctx, moduleId, tvDevice, tvDeviceType, config );

		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().getHostName();
			port = socketAddress.getPort();
		}

		if( Utils.isBlankString( host ) )
		{
			host = "api.commpass.tv";
		}

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

		URL url = new URL( config.get( "om.url", config.get( "commpass.api.url", "https://" + host + ":" + port + "/" ) ) );

		logger.info( "URL: " + url );

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

		this.jsonClient = new JsonClient( url, login, password );
		this.jsonClient.setContentType( "application/json" );

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

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

		alwaysEnableProductIds = Utils.toIntegerSet( config.get( "om.account.alwaysEnabledProductIds", null ) );
		if( alwaysEnableProductIds != null && alwaysEnableProductIds.size() == 0 )
		{
			alwaysEnableProductIds = null;
		}

		baseProductIdentifiers = new HashSet<Long>( Utils.toLongList( config.get( "om.product.baseProductIdentifiers", null ) ) );
		if( baseProductIdentifiers != null && baseProductIdentifiers.size() == 0 )
		{
			baseProductIdentifiers = null;
		}

		return null;
	}

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

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

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

		return null;
	}

	@Override
	public Object disconnect( final ServerContext ctx )
		throws Exception
	{
		try
		{
			contractManager.close();
			contractManager = null;

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

		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( final AccountOrderEvent e, final ServerContext ctx )
		throws Exception
	{
		logger.info( "accountModify" );

		try
		{
			accountModify0( e, ctx );
		}
		catch( JsonClientException ex )
		{
			if( ex.getResponseCode() == 404 )
			{
				logger.error( "Commpass return 404 with message: " + ex.getData() + ". Seems subscriber not exists in MW" );
				return false;
			}

			throw ex;
		}

		return null;
	}

	/**
	 * Редактирование и создание аккаунта.
	 * @param e
	 * @param ctx
	 * @return userId
	 * @throws Exception
	 */
	private long accountModify0( final AccountOrderEvent e, final ServerContext ctx )
		throws Exception
	{
		logger.info( "accountModify0" );

		long subscriberId;

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

		final Set<Long> packagesToAdd = TvDynUtils.getFullSetToEnable( e, o -> Utils.parseLong( o ), serviceMode, true );

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

		logger.info( "Need packageIds: " + packagesToAdd );

		// добавление аккаунта
		if( e.getOldTvAccount() == null || Utils.isBlankString( e.getOldTvAccount().getDeviceAccountId() ) )
		{
			final Contract contract = contractManager.getContractById( e.getContractId() );

			final String[] name = TvDynUtils.getName( contractDao, contract, customerLastNamePid, customerFirstNamePid, customerMiddleNamePid );

			JSONObject subscriber = new JSONObject();
			subscriber.put( "password", tvAccount.getPassword() );
			subscriber.put( "surname", name[1] );
			subscriber.put( "first_name", name[2] );
			subscriber.put( "middle_name", name[3] );

			if( alwaysEnableProductIds != null && !Collections.disjoint( alwaysEnableProductIds, e.getNewDeviceOptionIds() ) )
			{
				subscriber.put( "disabled", false );
			}
			else
			{
				subscriber.put( "disabled", e.getNewState() == TvAccount.STATE_ENABLE ? false : true );
			}

			subscriber.put( "max_terminal", 3 );
			subscriber.put( "address", "" );
			subscriber.put( "phone", "" );
			//subscriber.put( "comment", "contractId: " + tvAccount.getContractId() + ", tvAccountId: " + tvAccount.getId() );
			subscriber.put( "comment", "" );
			subscriber.put( "external_id", tvAccount.getContractId() + "-" + tvAccount.getId() );

			subscriber.put( "packages", packagesToAdd );

			JSONArray result = invokeAndGetArray( Method.put, "subscriber", null, subscriber );
			subscriber = result.getJSONObject( 0 );

			subscriberId = subscriber.getLong( "id" );

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

			try
			{
				TvAccount newTvAccount = tvAccountDao.get( e.getNewTvAccount().getId() );
				if( newTvAccount != null )
				{
					TvAccount oldTvAccount = newTvAccount.clone();

					tvAccount.setLogin( subscriber.getString( "username" ) );
					tvAccount.setPassword( subscriber.getString( "password" ) );
					tvAccount.setTitle( "Аккаунт: " + subscriber.getString( "username" ) );
					tvAccountDao.update( tvAccount );

					EventProcessor.getInstance().publish( new TvAccountModifiedEvent( moduleId, e.getContractId(), 0, oldTvAccount, tvAccount ) );
				}
			}
			catch( Exception ex )
			{
				logger.error( ex.getMessage(), ex );
			}
		}
		// редактирование аккаунта
		else
		{
			subscriberId = Utils.parseLong( e.getOldTvAccount().getDeviceAccountId() );

			final Contract contract = contractManager.getContractById( e.getContractId() );

			final String[] name = TvDynUtils.getName( contractDao, contract, customerLastNamePid, customerFirstNamePid, customerMiddleNamePid );
			
			/*if( Utils.notBlankString( e.getOldTvAccount().getLogin() ) && !e.getOldTvAccount().getLogin().equals( e.getNewTvAccount().getLogin() ) )
			{
				try
				{
					TvAccount newTvAccount = tvAccountDao.get( e.getNewTvAccount().getId() );
					if( newTvAccount != null )
					{
						TvAccount oldTvAccount = newTvAccount.clone();
						
						newTvAccount.setLogin( e.getOldTvAccount().getLogin() );
						newTvAccount.setTitle( "Аккаунт: " + newTvAccount.getLogin() );
						tvAccountDao.update( newTvAccount );

						EventProcessor.getInstance().publish( new TvAccountModifiedEvent( moduleId, e.getContractId(), 0, oldTvAccount, newTvAccount ) );
						
						tvAccount = newTvAccount;
					}
				}
				catch( Exception ex )
				{
					logger.error( ex.getMessage(), ex );
				}
			}*/

			JSONObject subscriber = new JSONObject();
			subscriber.put( "id", subscriberId );
			//subscriber.put( "username", tvAccount.getLogin() );
			subscriber.put( "password", tvAccount.getPassword() );
			subscriber.put( "surname", name[1] );
			subscriber.put( "first_name", name[2] );
			subscriber.put( "middle_name", name[3] );

			if( alwaysEnableProductIds != null && !Collections.disjoint( alwaysEnableProductIds, e.getNewDeviceOptionIds() ) )
			{
				subscriber.put( "disabled", false );
			}
			else
			{
				subscriber.put( "disabled", e.getNewState() == TvAccount.STATE_ENABLE ? false : true );
			}

			subscriber.put( "max_terminal", 3 );
			subscriber.put( "address", "" );
			subscriber.put( "phone", "" );
			//subscriber.put( "comment", "contractId: " + tvAccount.getContractId() + ", tvAccountId: " + tvAccount.getId() );
			subscriber.put( "comment", "" );
			subscriber.put( "external_id", tvAccount.getContractId() + "-" + tvAccount.getId() );

			subscriber.put( "packages", packagesToAdd );

			JSONArray result = invokeAndGetArray( Method.post, "subscriber", null, subscriber );
			subscriber = result.getJSONObject( 0 );

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

			try
			{
				TvAccount newTvAccount = tvAccountDao.get( e.getNewTvAccount().getId() );
				if( newTvAccount != null )
				{
					TvAccount oldTvAccount = newTvAccount.clone();

					tvAccount.setLogin( subscriber.getString( "username" ) );
					tvAccount.setPassword( subscriber.getString( "password" ) );
					tvAccount.setTitle( "Аккаунт: " + subscriber.getString( "username" ) );
					tvAccountDao.update( tvAccount );

					EventProcessor.getInstance().publish( new TvAccountModifiedEvent( moduleId, e.getContractId(), 0, oldTvAccount, tvAccount ) );
				}
			}
			catch( Exception ex )
			{
				logger.error( ex.getMessage(), ex );
			}
		}

		return subscriberId;
	}

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

		final long subscriberId = Utils.parseLong( e.getOldTvAccount().getDeviceAccountId() );

		try
		{
			Object result = invokeAndGetObject( Method.delete, "subscriber", "id/" + String.valueOf( subscriberId ), null );

			if( logger.isDebugEnabled() )
			{
				logger.debug( result );
			}
		}
		catch( JsonClientException ex )
		{
			if( ex.getResponseCode() == 404 )
			{
				logger.warn( "Commpass return 404 with message: " + ex.getData() );
				logger.warn( "Seems subscriber with ID=" + subscriberId + " not exists in MW" );
			}
		}

		return null;
	}

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

		/*final short deviceState;
		if( e instanceof AccountOrderEvent )
		{
			deviceState = ((AccountOrderEvent)e).getNewState();
		}
		else
		{
			deviceState = e.getTvAccountRuntime().getTvAccount().getDeviceState();
		}*/

		final Set<Long> packagesToAdd = TvDynUtils.getFullSetToEnable2( e, o -> Utils.parseLong( o ), serviceMode, true );

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

		if( baseProductIdentifiers != null && Collections.disjoint( baseProductIdentifiers, packagesToAdd ) )
		{
			logger.info( "Base products not found: " + baseProductIdentifiers );
			packagesToAdd.clear();
		}

		logger.info( "Need packageIds: " + packagesToAdd );

		JSONObject subscriber = new JSONObject();
		subscriber.put( "id", subscriberId );
		subscriber.put( "username", e.getTvAccountRuntime().getTvAccount().getLogin() );

		subscriber.put( "packages", packagesToAdd );
		
        try
        {
            JSONArray result = invokeAndGetArray( Method.post, "subscriber", null, subscriber );

            if( logger.isDebugEnabled() )
            {
                logger.debug( result );
            }
        }
        catch( JsonClientException ex )
        {
            if( ex.getResponseCode() != 404 )
            {
                throw ex;
            }
            
            logger.info( "Found error " + ex.getResponseCode() );
            
            try
            {
                JSONObject message = new JSONObject( ex.getData() );

                if( message.get( "error_message" ).toString().contains( "subscriber not found" ) )
                {
                    logger.error(  message.get( "error_message" ).toString() );
                    
                    return false;
                }
                else
                {
                    throw ex;
                }
            }
            catch( Exception ex2 )
            {
                logger.info( ex2.getMessage(), ex2 );
                throw ex;
            }
        }

        return null;
	}

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

		final long subscriberId = Utils.parseLong( e.getTvAccount().getDeviceAccountId() );

		return productsModifySyncFull( e, ctx, subscriberId );
	}

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

		final long subscriberId = Utils.parseLong( e.getTvAccountRuntime().getTvAccount().getDeviceAccountId() );

		return accountOptionsModify0( e, ctx, subscriberId );
	}

	private Object accountOptionsModify0( final AbstractOrderEvent e, final ServerContext ctx, final long subscriberId )
		throws Exception
	{
		logger.debug( "accountOptionsModify0" );

		return productsModifySyncFull( e, ctx, subscriberId );
	}

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

		if( e.getOldTvAccount() == null || Utils.isBlankString( e.getOldTvAccount().getDeviceAccountId() ) )
		{
			return accountModify( e, ctx );
		}

		long subscriberId = Utils.parseLong( e.getOldTvAccount().getDeviceAccountId() );

		accountStateModify0( e, subscriberId );

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

		return null;
	}

	private void accountStateModify0( final AccountOrderEvent e, final long subscriberId )
		throws Exception
	{
		logger.info( "accountStateModify0" );

		JSONObject subscriber = new JSONObject();
		subscriber.put( "id", subscriberId );
		subscriber.put( "username", e.getTvAccountRuntime().getTvAccount().getLogin() );

		if( alwaysEnableProductIds != null && !Collections.disjoint( alwaysEnableProductIds, e.getNewDeviceOptionIds() ) )
		{
			subscriber.put( "disabled", false );
		}
		else
		{
			subscriber.put( "disabled", e.getNewState() == TvAccount.STATE_ENABLE ? false : true );
		}

		JSONArray result = invokeAndGetArray( Method.post, "subscriber", null, subscriber );

		if( logger.isDebugEnabled() )
		{
			logger.debug( result );
		}
	}
}
