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

import java.text.DecimalFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;

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

import ru.bitel.bgbilling.apps.tv.access.om.AbstractOrderEvent;
import ru.bitel.bgbilling.common.BGException;
import ru.bitel.bgbilling.kernel.contract.api.common.bean.Contract;
import ru.bitel.bgbilling.kernel.contract.api.server.bean.ContractDao;
import ru.bitel.bgbilling.modules.tv.common.bean.TvAccount;
import ru.bitel.common.Utils;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductSpec;
import ru.bitel.oss.systems.inventory.service.common.bean.ServiceSpec;

public class TvDynUtils
{
	private static final Logger logger = LogManager.getLogger();

    public static String[] getName( final ContractDao contractDao, final Contract contract, final int customerLastNamePid, final int customerFirstNamePid, final int customerMiddleNamePid )
        throws BGException
    {
        final int contractId = contract.getId();

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

        if ( customerFirstNamePid > 0 )
        {
            firstName = contractDao.optContractParameterTextAsString( contractId, customerFirstNamePid ).orElse( "" );
            if ( customerMiddleNamePid > 0 )
            {
                middleName = contractDao.optContractParameterTextAsString( contractId, customerMiddleNamePid ).orElse( "" );
            }
        }
        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];
                }
            }
        }

        StringBuilder name = new StringBuilder();
        if ( Utils.notBlankString( lastName ) )
        {
            name.append( lastName ).append( ' ' );
        }

        if ( Utils.notBlankString( firstName ) )
        {
            name.append( firstName ).append( ' ' );
        }

        if ( Utils.notBlankString( middleName ) )
        {
            name.append( middleName ).append( ' ' );
        }

        if ( name.length() > 0 )
        {
            name.setLength( name.length() - 1 );
        }

        return new String[] { name.toString(), lastName, firstName, middleName };
    }
	
    public static String getName( final ContractDao contractDao, final Contract contract, final int customerCompanyPid )
        throws BGException
    {
        String name = customerCompanyPid > 0 ? contractDao.optContractParameterTextAsString( contract.getId(), customerCompanyPid ).orElse( null ) : "";
        if ( Utils.isEmptyString( name ) )
        {
            name = contract.getComment();
        }
        return name;
    }

	public static <K> Set<K> getFullSetToEnable( final AbstractOrderEvent e, final Function<String, K> converter, boolean serviceMode, boolean includeOptions )
		throws BGException
	{
		final Set<K> result = new HashSet<K>();

		if ( serviceMode )
		{
			// получаем полный список активных сервисов
			for ( ServiceSpec serviceSpec : e.getFullServiceSpecSetToEnable() )
			{
				try
				{
					final K k = converter.apply( serviceSpec.getIdentifier().trim() );
					if ( k != null )
					{
						logger.info( "Service: " + serviceSpec + " => " + k );

						result.add( k );
					}
				}
				catch( Exception ex )
				{
					logger.error( ex.getMessage(), ex );
				}
			}
		}
		else
		{
			// получаем список активных продуктов
			for ( ProductSpec productSpec : e.getFullProductSpecSetToEnable() )
			{
				try
				{
					final K k = converter.apply( productSpec.getIdentifier().trim() );
					if ( k != null )
					{
						logger.info( "Product: " + productSpec + " => " + k );

						result.add( k );
					}
				}
				catch( Exception ex )
				{
					logger.error( ex.getMessage(), ex );
				}
			}
		}

		if ( includeOptions )
		{
			// добавляем продукты-опции
			for ( ProductSpec productSpec : e.getNewDeviceOptionProductSpecs() )
			{
				try
				{
					final K k = converter.apply( productSpec.getIdentifier().trim() );
					if ( k != null )
					{
						logger.info( "Product (option): " + productSpec + " => " + k );

						result.add( k );
					}
				}
				catch( Exception ex )
				{
					logger.error( ex.getMessage(), ex );
				}
			}
		}

		return result;
	}

	public static String getEmail( ContractDao contractDao, int contractId, int parameterId )
	    throws BGException
	{
        if ( contractId <= 0 )
        {
            throw new IllegalArgumentException();
        }

        if ( parameterId <= 0 )
        {
            return null;
        }

        return contractDao.optContractParameterEmailFirst( contractId, parameterId )
            .orElse( contractDao.optContractParameterTextAsString( contractId, parameterId ).orElse( null ) );
	}

	public static <K> Set<K> getFullSetToEnable2( final AbstractOrderEvent e, final Function<String, K> converter, boolean serviceMode, boolean includeOptions )
		throws BGException
	{
		final Set<K> result = new HashSet<K>();

		if ( serviceMode )
		{
			// получаем полный список активных сервисов
			for ( ServiceSpec serviceSpec : e.getFullServiceSpecSetToEnable() )
			{
				try
				{
					final K k = converter.apply( serviceSpec.getIdentifier().trim() );
					if ( k != null )
					{
						logger.info( "Service: " + serviceSpec + " => " + k );

						result.add( k );
					}
				}
				catch( Exception ex )
				{
					logger.error( ex.getMessage(), ex );
				}
			}
		}
		else
		{
			// получаем список активных продуктов
			Set<ProductSpec> productSpecSet =  e.getFullProductSpecSetToEnable();
			productSpecSet = filterProductSpecByDepends( productSpecSet );
			for ( ProductSpec productSpec : productSpecSet )
			{
				try
				{
					final K k = converter.apply( productSpec.getIdentifier().trim() );
					if ( k != null )
					{
						logger.info( "Product: " + productSpec + " => " + k );

						result.add( k );
					}
				}
				catch( Exception ex )
				{
					logger.error( ex.getMessage(), ex );
				}
			}
		}

		if ( includeOptions )
		{
			// добавляем продукты-опции
			for ( ProductSpec productSpec : e.getNewDeviceOptionProductSpecs() )
			{
				try
				{
					final K k = converter.apply( productSpec.getIdentifier().trim() );
					if ( k != null )
					{
						logger.info( "Product (option): " + productSpec + " => " + k );

						result.add( k );
					}
				}
				catch( Exception ex )
				{
					logger.error( ex.getMessage(), ex );
				}
			}
		}

		return result;
	}

	public static Set<ProductSpec> filterProductSpecByDepends( Set<ProductSpec> set )
		throws BGException
	{
		Map<Integer, ProductSpec> map = new HashMap<Integer, ProductSpec>();

		for ( ProductSpec productSpec : set )
		{
			map.put( productSpec.getId(), productSpec );
		}

		boolean needCheck;
		Map<Integer, ProductSpec> result;

		do
		{
			needCheck = false;
			
			result = new HashMap<Integer, ProductSpec>( map );

			for ( ProductSpec productSpec : map.values() )
			{
				if ( productSpec.getDepends() != null && productSpec.getDepends().size() > 0 )
				{
					if ( Collections.disjoint( productSpec.getDepends(), result.keySet() ) )
					{
						if ( result.remove( productSpec.getId() ) != null )
						{
							needCheck = true;
						}
					}
				}
			}

			map = result;
		}
		while( needCheck );

		return new HashSet<ProductSpec>( result.values() );
	}
	
	private static final Pattern digits = Pattern.compile( "\\A[0-9]+\\z" );

    /**
     * Получение отформатированного логина.
     * 
     * @param tvAccount
     * @return значение поля логин, если указано, иначе отформатированный tv_account.id
     */
    public static String getLogin( final TvAccount tvAccount, String loginFormat )
    {
        assert tvAccount.getParentId() > 0;

        if ( Utils.notBlankString( tvAccount.getLogin() ) )
        {
            if ( Utils.notBlankString( loginFormat ) && digits.matcher( tvAccount.getLogin() ).matches() )
            {
                final long login = Utils.parseLong( tvAccount.getLogin(), -1 );
                if ( login > 0 )
                {
                    return new DecimalFormat( loginFormat ).format( login );
                }
            }

            return tvAccount.getLogin();
        }

        if ( Utils.notBlankString( loginFormat ) )
        {
            return new DecimalFormat( loginFormat ).format( tvAccount.getId() );
        }

        return String.valueOf( tvAccount.getId() );
    }
}
