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

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.sql.Connection;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

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.ProductEntry;
import ru.bitel.bgbilling.apps.tv.access.om.ProductOrderEvent;
import ru.bitel.bgbilling.common.BGException;
import ru.bitel.bgbilling.kernel.base.server.logger.BGLogger;
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.server.bean.ContractDao;
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.common.ParameterMap;
import ru.bitel.common.Utils;
import ru.bitel.oss.kernel.entity.common.bean.EntityAttr;
import ru.bitel.oss.kernel.entity.common.bean.EntityAttrText;
import ru.bitel.oss.systems.inventory.product.common.bean.Product;

public class SmartyOrderManager
    extends BGLogger
    implements OrderManager
{
    private static final String PARAM_ABONEMENT = "abonement";
    private static final String PARAM_ACCOUNT_ID = "account_id";
    private static final String PARAM_CLIENT_ID = "client_id";
    private static final String PARAM_DEVICE_ID = "device_id";
    private static final String PARAM_DEVICE_UID = "device_uid";
    private static final String PARAM_TEXT = "text";
    private static final String PARAM_SUBJECT = "subject";
    private static final String PARAM_URGENT = "urgent";
    private static final String PARAM_ID = "id";
    private static final String PARAM_TARIFF_ID = "tariff_id";
    private static final String PARAM_CUSTOMER_ID = "customer_id";
    private static final String PARAM_EXT_ID = "ext_id";
    private static final String PARAM_SERVICE_ID = "service_id";
    private static final String PARAM_DELETE_AFTER_READING = "delete_after_reading";
    private static final String PARAM_COMMENT = "comment";
    private static final String PARAM_FIRSTNAME = "firstname"; 
    private static final String PARAM_MIDDLENAME = "middlename"; 
    private static final String PARAM_LASTNAME = "lastname"; 
    private static final String PARAM_CONTRACT_NUMBER = "contract_number";
    private static final String PARAM_PASSWORD = "password";

    private SmartyConf conf = null;
    
    public SmartyOrderManager()
    {
    }
    
    /**
     * Account - AccountActivate: активация аккаунта
     * @param accountId Account ID.
     * @param abonement Номер абонемента аккаунта. Должен быть передан либо abonement, либо account_id.
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountActivate( String accountId, String abonement )
        throws Exception
    {
        Map<String,String> params = new HashMap<>();
        if ( accountId != null )
        {
            params.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            params.put( PARAM_ABONEMENT, abonement );
        }
        params.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/activate", params, SmartyResponse.class );
    }
    
    /**
     * 
     */
    public SmartyResponse doAccountCreate( Map<String,String> params )
        throws Exception
    {
        params.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/create", params, SmartyResponse.class );
    }
    
    /**
     * Account - AccountDeactivate: деактивация аккаунта 
     * @param accountId Account ID.
     * @param abonement Номер абонемента аккаунта. Должен быть передан либо abonement, либо account_id.
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountDeactivate( String accountId, String abonement )
        throws Exception
    {
        Map<String,String> params = new HashMap<>();
        if ( accountId != null )
        {
            params.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            params.put( PARAM_ABONEMENT, abonement );
        }
        params.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/deactivate", params, SmartyResponse.class );
    }
    
    /**
     * Account - AccountDelete: удаление аккаунта
     * @param accountId Account ID.
     * @param abonement Номер абонемента аккаунта. Должен быть передан либо abonement, либо account_id.
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountDelete( String accountId, String abonement )
        throws Exception
    {
        Map<String, String> params = new HashMap<>();
        if ( accountId != null )
        {
            params.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            params.put( PARAM_ABONEMENT, abonement );
        }
        params.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/delete", params, SmartyResponse.class );
    }
    
    /**
     * Account - AccountDeviceCreate: привязка устройства к аккаунту
     * @param accountId Account ID.
     * @param abonement Номер абонемента аккаунта. Должен быть передан либо abonement, либо account_id.
     * @param systemName Системное имя устройства.
     * @param params
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountDeviceCreate( String accountId, String abonement, String systemName, Map<String, String> params )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( accountId != null )
        {
            requestParams.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            requestParams.put( PARAM_ABONEMENT, abonement );
        }
        requestParams.putAll( params != null ? params : new HashMap<>() );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/device/create", requestParams, SmartyResponse.class );
    }
    
    /**
     * Account - AccountDeviceDelete: удаление привязанного устройства
     * @param deviceId Идентификатор устройства.
     * @param deviceUid UID устройства. Должен быть передан либо device_uid, либо device_id.
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountDeviceDelete( String deviceId, String deviceUid )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( deviceId != null )
        {
            requestParams.put( PARAM_DEVICE_ID, deviceId );
        }
        if ( deviceUid != null )
        {
            requestParams.put( PARAM_DEVICE_UID, deviceUid );
        }
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/device/delete", requestParams, SmartyResponse.class );
    }
    
    /**
     * Account - AccountInfo: получение информации об аккаунте
     * @param accountId Account ID.
     * @param abonement Номер абонемента аккаунта. Должен быть передан либо abonement, либо account_id.
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountInfo( String accountId, String abonement )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( accountId != null )
        {
            requestParams.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            requestParams.put( PARAM_ABONEMENT, abonement );
        }
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/info", requestParams, SmartyResponse.class );
    }
    
//    Допустимые значения: 0, 1
//    delete_after_reading    Number  
//
//    Удалить сообщение после прочтения.
//
//    Допустимые значения: 0, 1
    /**
     * Account - AccountMessageCreate: Создание сообщения для аккаунта
     * @param accountId
     * @param abonement
     * @param text Текст сообщения.
     * @param subject Тема сообщения.
     * @param urgent Срочное
     * @param deleteAfterReading
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountMessageCreate( String accountId, String abonement, String text, String subject, boolean urgent, boolean deleteAfterReading )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( accountId != null )
        {
            requestParams.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            requestParams.put( PARAM_ABONEMENT, abonement );
        }
        requestParams.put( PARAM_TEXT, text );
        requestParams.put( PARAM_SUBJECT, subject );
        requestParams.put( PARAM_URGENT, urgent ? "1" : "0" );
        requestParams.put( PARAM_DELETE_AFTER_READING, deleteAfterReading ? "1" : "0" );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/message/create", requestParams, SmartyResponse.class );
    }
    
    /**
     * Account - AccountModify: изменение аккаунта
     * @param accountId
     * @param abonement
     * @param params
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountModify( String accountId, String abonement, Map<String, String> params )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( accountId != null )
        {
            requestParams.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            requestParams.put( PARAM_ABONEMENT, abonement );
        }
        requestParams.putAll( params != null ? params : new HashMap<>() );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/modify", requestParams, SmartyResponse.class );
    }    
    
    //
    /**
     * Account - AccountTariffAssign: подключение тарифного пакета аккаунта
     * @param accountId Account ID.
     * @param abonement Номер абонемента аккаунта. Должен быть передан либо abonement, либо account_id.
     * @param tariffId Идентификатор тарифного пакета в Smarty.
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountTariffAssign( String accountId, String abonement, String tariffId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( accountId != null )
        {
            requestParams.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            requestParams.put( PARAM_ABONEMENT, abonement );
        }
        requestParams.put( PARAM_TARIFF_ID, tariffId );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/tariff/assign", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Account - AccountTariffRemove: отключение тарифного пакета у аккаунта
     * @param accountId Account ID. 
     * @param abonement Номер абонемента аккаунта. Должен быть передан либо abonement, либо account_id. 
     * @param tariffId Идентификатор тарифного пакета в Smarty.
     * @return
     * @throws Exception
     */
    public SmartyResponse doAccountTariffRemove( String accountId, String abonement, String tariffId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( accountId != null )
        {
            requestParams.put( PARAM_ACCOUNT_ID, accountId );
        }
        if ( abonement != null )
        {
            requestParams.put( PARAM_ABONEMENT, abonement );
        }
        requestParams.put( PARAM_TARIFF_ID, tariffId );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/account/tariff/remove", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Customer - CustomerCreate: создание абонента
     * @param params
     * @return
     * @throws Exception
     */
    public SmartyResponse doCustomerCreate( Map<String, String> params )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.putAll( params != null ? params : new HashMap<>() );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/customer/create", requestParams, SmartyResponse.class );
    }    

    /**
     * Customer - CustomerDelete: удаление абонента
     * 
     * @param customerId
     * @param extId
     * @return
     * @throws Exception
     */
    public SmartyResponse doCustomerDelete( String customerId, String extId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( customerId != null )
        {
            requestParams.put( PARAM_CUSTOMER_ID, customerId );
        }
        if ( extId != null )
        {
            requestParams.put( PARAM_EXT_ID, extId );
        }
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/customer/delete", requestParams, SmartyResponse.class );
    }    

    /**
     * Customer - CustomerInfo: информация об абоненте
     * @param customerId Customer ID.
     * @param extId Уникальный идентификатор абонента в биллинговой системе провайдера. Должен быть передан либо ext_id, либо customer_id. В случае, если передан и ext_id, и customer_id, то ext_id будет перезаписан.
     * @return
     * @throws Exception
     */
    public SmartyResponse doCustomerInfo( String customerId, String extId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( customerId != null )
        {
            requestParams.put( PARAM_CUSTOMER_ID, customerId );
        }
        if ( extId != null )
        {
            requestParams.put( PARAM_EXT_ID, extId );
        }
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/customer/info", requestParams, SmartyResponse.class );
    }    

    /**
     * Customer - CustomerModify: изменение абонента
     * @param customerId Customer ID.
     * @param extId Уникальный идентификатор абонента в биллинговой системе провайдера. Должен быть передан либо ext_id, либо customer_id. В случае, если передан и ext_id, и customer_id, то ext_id будет перезаписан.
     * @param params
     * @return
     * @throws Exception
     */
    public SmartyResponse doCustomerModify( String customerId, String extId, Map<String, String> params )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( customerId != null )
        {
            requestParams.put( PARAM_CUSTOMER_ID, customerId );
        }
        if ( extId != null )
        {
            requestParams.put( PARAM_EXT_ID, extId );
        }
        requestParams.putAll( params != null ? params : new HashMap<>() );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/customer/modify", requestParams, SmartyResponse.class );
    }
    
    /**
     * Customer - CustomerTariffAssign: подключение тарифного пакета абоненту
     * @param customerId
     * @param extId
     * @param tariffId
     * @return
     * @throws Exception
     */
    public SmartyResponse doCustomerTariffAssign( String customerId, String extId, int tariffId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( customerId != null )
        {
            requestParams.put( PARAM_CUSTOMER_ID, customerId );
        }
        if ( extId != null )
        {
            requestParams.put( PARAM_EXT_ID, extId );
        }
        requestParams.put( PARAM_TARIFF_ID, String.valueOf( tariffId ) );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/customer/tariff/assign", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Customer - CustomerTariffRemove: отключение тарифного пакета у абонента
     * @param customerId
     * @param extId
     * @param tariffId
     * @return
     * @throws Exception
     */
    public SmartyResponse doCustomerTariffRemove( String customerId, String extId, int tariffId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        if ( customerId != null )
        {
            requestParams.put( PARAM_CUSTOMER_ID, customerId );
        }
        if ( extId != null )
        {
            requestParams.put( PARAM_EXT_ID, extId );
        }
        requestParams.put( PARAM_TARIFF_ID, String.valueOf( tariffId ) );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/customer/tariff/remove", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Tariff - TariffCreate: создание тарифного пакета
     * @param params
     * @return
     * @throws Exception
     */
    public SmartyResponse doTariffCreate( Map<String, String> params )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.putAll( params != null ? params : new HashMap<>() );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/tariff/create", requestParams, SmartyResponse.class );
    }
    
    /**
     * Tariff - TariffDelete: удаление тарифного пакета
     * @param id Идентификатор тарифного пакета.
     * @return
     * @throws Exception
     */
    public SmartyResponse doTariffDelete( String id )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put( PARAM_ID, id );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/tariff/delete", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Tariff - TariffList: Cписок доступных тарифных пакетов в Smarty
     * @return
     * @throws Exception
     */
    public SmartyResponse doTariffList()
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/tariff/list", requestParams, SmartyResponse.class );
    }
    
    /**
     * Tariff - TariffModify: изменение тарифного пакета
     * @param params
     * @return
     * @throws Exception
     */
    public SmartyResponse doTariffModify( Map<String, String> params )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.putAll( params != null ? params : new HashMap<>() );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/tariff/modify", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Tariff - TariffStreamServiceAssign: привязка стриминг-сервиса к тарифному пакету
     * @param tariffId
     * @param serviceId
     * @return
     * @throws Exception
     */
    public SmartyResponse doTariffStreamServiceAssign( int tariffId, int serviceId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put( PARAM_TARIFF_ID, String.valueOf( tariffId ) );
        requestParams.put( PARAM_SERVICE_ID, String.valueOf( serviceId ) );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/tariff/streamservice/assign", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Tariff - TariffStreamServiceRemove: отвязка стриминг-сервиса от тарифного пакета
     * @param tariffId Идентификатор тарифного пакета.
     * @param serviceId Идентификатор стриминг-сервиса.
     * @return
     * @throws Exception
     */
    public SmartyResponse doTariffStreamServiceRemove( int tariffId, int serviceId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put( PARAM_TARIFF_ID, String.valueOf( tariffId ) );
        requestParams.put( PARAM_SERVICE_ID, String.valueOf( serviceId ) );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/tariff/streamservice/remove", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Transaction - TransactionCreate: создание финансовой операции
     * @param extId Уникальный идентификатор транзакции в биллинговой системе провайдера.
     * @param params
     * @return
     * @throws Exception
     */
    public SmartyResponse doTransactionCreate( String extId, Map<String, String> params )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put( PARAM_EXT_ID, String.valueOf( extId ) );
        requestParams.putAll( params != null ? params : new HashMap<>() );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/transaction/create", requestParams, SmartyResponse.class );
    }    
    
    /**
     * Transaction - TransactionDelete: удаление финансовой операции
     * @param extId
     * @return
     * @throws Exception
     */
    public SmartyResponse doTransactionCreate( String extId )
        throws Exception
    {
        Map<String, String> requestParams = new HashMap<>();
        requestParams.put( PARAM_EXT_ID, String.valueOf( extId ) );
        requestParams.put( PARAM_CLIENT_ID, conf.clientId );
        return doRequest( "/billing/api/transaction/delete", requestParams, SmartyResponse.class );
    }    
    
    private <T> T doRequest( String request,  Map<String,String> params, Class<T> clazz )
        throws Exception
    {
        params.put( "signature", doSign( params, conf.secretKey ) );

        URL url = URI.create( conf.serverUrl + request ).toURL();
        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
        connection.setConnectTimeout( conf.connectTimeout );
        connection.setReadTimeout( conf.readTimeout );
        connection.setRequestMethod( "POST" );
//        if ( requestProperties != null && !requestProperties.isEmpty() )
//        {
//            for ( Map.Entry<String, String> entry : requestProperties.entrySet() )
//            {
//                connection.setRequestProperty( entry.getKey(), entry.getValue() );
//            }
//        }
        connection.setDoOutput( true );
        connection.setDoInput( true );

        StringBuffer outputText = new StringBuffer();
        params.keySet().forEach( e -> outputText.append( e ).append( "=" ).append( encodeString( params.get( e ) ) ).append( "&" ) );
        
        if ( getLogger().isDebugEnabled() )
        {
            getLogger().debug( "ЗАПРОС: " + conf.serverUrl + request );
            getLogger().debug( "Параметры запроса в MW: \n\t" + outputText.toString() );
        }
        
        // пишем в данные POST
        BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( connection.getOutputStream() ) );
        bw.write( outputText.toString().toCharArray() );
        bw.close();
        //
        connection.connect();

        InputStream inputStream = connection.getResponseCode() == 200 ? connection.getInputStream() : connection.getErrorStream();
        
        // читаем ответ
        StringBuilder result = null;
        try (BufferedReader br = new BufferedReader( new InputStreamReader( inputStream ) ))
        {
            if ( br.ready() )
            {
                String line = null;
                result = new StringBuilder();
                while ( (line = br.readLine()) != null )
                {
                    result.append( line );
                }
            }
        }
        //
        connection.disconnect();

        if ( getLogger().isDebugEnabled() )
        {
            getLogger().debug( "ОТВЕТ из MW: \n\t" + outputText.toString() );
        }
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule( new JavaTimeModule() );
        return (T)mapper.readValue( (String)(result != null ? result.toString() : null), clazz );
    }
    
    private String encodeString( String string )
    {
        try
        {
            return URLEncoder.encode( string, "UTF-8" );
        }
        catch( Exception e )
        {
            getLogger().error( "string = " + string, e );
        }
        return "";
    }

    /**
     * Формирование подписи
     * @param params
     * @param secretKey
     * @return
     * @throws UnsupportedEncodingException 
     */
    public static String doSign( Map<String,String> params, String secretKey )
        throws UnsupportedEncodingException
    {
        List<String> keys = params.keySet().stream().collect( Collectors.toList() );
        Collections.sort( keys );
        StringBuffer buffer = new StringBuffer();
        keys.forEach( e -> buffer.append( e ).append( ":" ).append( params.get( e ) ).append( ";" ) );
        buffer.append( secretKey );
        byte[] bytes = Base64.getEncoder().encode( buffer.toString().getBytes( "UTF-8" ) );
        return Utils.bytesToHexString( Utils.getDigestBytes( bytes ) ).toLowerCase();
    }

    
    
    
    
    
    @Override
    public Object init( ServerContext serverContext, int moduleId, TvDevice tvDevice, TvDeviceType tvDeviceType, ParameterMap config )
        throws Exception
    {
        this.conf = serverContext.getSetup().getConfig( moduleId, SmartyConf.class );
        return null;
    }

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

    @Override
    public Object connect( ServerContext serverContext )
        throws Exception
    {
        return null;
    }

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

    @Override
    public Object accountCreate( AccountOrderEvent accountOrderEvent, ServerContext serverContext )
        throws Exception
    {
        getLogger().debug( "accountCreate" );

        Connection con = serverContext.getConnection();
        final int contractId = accountOrderEvent.getContractId();
        final String extId = conf.modulePrefix + contractId;
        final String login = accountOrderEvent.getNewTvAccount().getLogin();
        
        // проверим есть ли customer в MW
        SmartyResponse smartyResponse = doCustomerInfo( null, extId );
        if ( getLogger().isDebugEnabled() )
        {
            getLogger().debug( "doCustomerInfo(...): smartyResponse.getError() = " + smartyResponse.getError() + ( smartyResponse.getError() != 0 ? "; smartyResponse.getErrorMessage() = " + smartyResponse.getErrorMessage() : "" ) );
        }
        
        if ( smartyResponse.getError() == 1 )
        {
            // Абонент не найден, создаем...
            String contractTitleComment = "ID#" + contractId, contractNumber = "";
            try ( ContractDao contractDao = new ContractDao( con, User.USER_SERVER ) )
            {
                Contract contract = contractDao.get( contractId );
                if ( contract != null )
                {
                    contractNumber = contract.getTitle();
                    contractTitleComment = contract.getTitle() + " [" + contract.getComment() + "]";
                }
                Map<String, String> params = new HashMap<>();
                params.put( PARAM_EXT_ID, extId );
                params.put( PARAM_COMMENT, contractTitleComment );
                if ( Utils.notBlankString( contractNumber ) )
                {
                    params.put( PARAM_CONTRACT_NUMBER, contractNumber );
                }
                setParam( contractId, conf.paramFirstnameId, PARAM_FIRSTNAME, params, contractDao );
                setParam( contractId, conf.paramMiddlenameId, PARAM_MIDDLENAME, params, contractDao );
                setParam( contractId, conf.paramLastnameId, PARAM_LASTNAME, params, contractDao );
                smartyResponse = doCustomerCreate( params );
                logError( smartyResponse, "doCustomerCreate(%s, %s)", contractTitleComment, extId );
            }
        }
        
        if ( smartyResponse.getError() == 0 )
        {
            // создаем аккаунт в MW
            Map<String, String> params = new HashMap<>();
            params.put( PARAM_EXT_ID, extId );
            params.put( PARAM_ABONEMENT, login );
            params.put( PARAM_PASSWORD, accountOrderEvent.getNewTvAccount().getPassword() );
            smartyResponse = doAccountCreate( params );
            logError( smartyResponse, "doAccountCreate(%s, %s)", extId, login );
        }
        
        if ( smartyResponse.getError() == 0 )
        {
            // активация/деактивация аккаунта
            if ( accountOrderEvent.getNewTvAccount().getStatus() == TvAccount.STATUS_ACTIVE )
            {
                smartyResponse = doAccountActivate( null, login );
                logError( smartyResponse, "doAccountActivate(%s)", login );
            }
            else
            {
                smartyResponse = doAccountDeactivate( null, login );                
                logError( smartyResponse, "doAccountDeactivate(%s)", login );
            }
        }        
            
        return null;
    }
    
    private void setParam( int contractId, int paramId, String paramName,  Map<String, String> params, ContractDao contractDao )
        throws BGException
    {
        if ( paramId > 0 )
        {
            EntityAttr entityAttr = contractDao.getContractParameter( contractId, paramId );
            if ( entityAttr instanceof EntityAttrText )
            {
                params.put( paramName, ((EntityAttrText)entityAttr).getValue() );
            }
        }
    }

    @Override
    public Object accountModify( AccountOrderEvent accountOrderEvent, ServerContext serverContext )
        throws Exception
    {
        getLogger().debug( "accountModify" );

        final String login = accountOrderEvent.getNewTvAccount().getLogin();
        
        // правим аккаунт в MW
        Map<String, String> params = new HashMap<>();
        params.put( PARAM_PASSWORD, accountOrderEvent.getNewTvAccount().getPassword() );
        SmartyResponse smartyResponse = doAccountModify( null, login, params );
        logError( smartyResponse, "doAccountModify(%s)", login );
        
        return null;
    }

    @Override
    public Object accountRemove( AccountOrderEvent accountOrderEvent, ServerContext serverContext )
        throws Exception
    {
        getLogger().info( "accountRemove" );
        
        final String login = accountOrderEvent.getOldTvAccount().getLogin();

        // деактивируем аккаунт в MW
        SmartyResponse smartyResponse = doAccountDeactivate( null, login );                
        logError( smartyResponse, "doAccountDeactivate(%s)", login );
        
        if ( smartyResponse.getError() == 0 )
        {
            // Удаляем аккаунт в MW
            smartyResponse = doAccountDelete( null, login );
            logError( smartyResponse, "doAccountDelete(%s)", login );
        }        
        
        return null;
    }

    @Override
    public Object accountStateModify( AccountOrderEvent accountOrderEvent, ServerContext serverContext )
        throws Exception
    {
        getLogger().info( "accountStateModify" );

        final String login = accountOrderEvent.getOldTvAccount().getLogin();

        // активация/деактивация аккаунта
        if ( accountOrderEvent.getNewState() == TvAccount.STATE_ENABLE )
        {
            SmartyResponse smartyResponse = doAccountActivate( null, login );
            logError( smartyResponse, "doAccountActivate(%s)", login );
        }
        else
        {
            SmartyResponse smartyResponse = doAccountDeactivate( null, login );                
            logError( smartyResponse, "doAccountDeactivate(%s)", login );
        }

        return null;
    }

    @Override
    public Object accountOptionsModify( AbstractOrderEvent abstractOrderEvent, ServerContext serverContext )
        throws Exception
    {
        getLogger().info( "accountOptionsModify" );
        return null;
    }

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

        final String login = productOrderEvent.getTvAccount().getLogin();
        
        if ( getLogger().isDebugEnabled() )
        {
            getLogger().debug( "login = " + login );
            getLogger().debug( "productOrderEvent.getProductEntryList().size() = " + productOrderEvent.getProductEntryList().size() );
        }

        for ( ProductEntry productEntry : productOrderEvent.getProductEntryList() )
        {
            String productIdentifier = productEntry.getProductSpec().getIdentifier();
            if ( getLogger().isDebugEnabled() )
            {
                getLogger().debug( "productEntry = " + productEntry );
                getLogger().debug( "productIdentifier = " + productIdentifier );
                String state = "?";
                switch ( productEntry.getNewState() )
                {
                    case Product.STATE_DISABLED: state = "STATE_DISABLED"; break;
                    case Product.STATE_REMOVED:  state = "STATE_REMOVED";  break;
                    case Product.STATE_ENABLED:  state = "STATE_ENABLED";  break;
                    default: break;
                }
                getLogger().debug( "productEntry.getNewState() = " + productEntry.getNewState() + " [" + state + "]" );
            }
            if ( productEntry.getNewState() == Product.STATE_DISABLED || productEntry.getNewState() == Product.STATE_REMOVED )
            {
                SmartyResponse smartyResponse = doAccountTariffRemove( null, login, productIdentifier );
                logError( smartyResponse, "doAccountTariffRemove(%s, %s)", login, productIdentifier );
            }
            else
            {
                SmartyResponse smartyResponse = doAccountTariffAssign( null, login, productIdentifier );
                logError( smartyResponse, "doAccountTariffAssign(%s, %s)", login, productIdentifier );
            }
        }
        
        return null;
    }
    
//    private void getEmailPhone( Connection con, int contractId, SmartyUser smartyUser )
//        throws BGException
//    {
//        try (ContractParameterManager contractParameterManager = new ContractParameterManager( con ))
//        {
//            if ( conf.paramEmailId > 0 )
//            {
//                smartyUser.email = TvDynUtils.getEmail( contractParameterManager, contractId, conf.paramEmailId );
//            }
//    
//            if ( conf.paramPhoneId > 0 )
//            {
//                PhoneParamValue phoneValue = contractParameterManager.getPhoneParam( contractId, conf.paramPhoneId );
//                if ( phoneValue != null && phoneValue.size() > 0 )
//                {
//                    smartyUser.phone = phoneValue.getPhoneItem( 0 ).getPhone();
//                }
//            }
//        }
//    }
    
    private void logError( SmartyResponse smartyResponse, String format, Object... data )
    {
        if ( smartyResponse.getError() > 0 )
        {
            getLogger().error( String.format( format, data ) + ": smartyResponse.getError() = " + smartyResponse.getError() + ( smartyResponse.getError() != 0 ?"; smartyResponse.getErrorMessage() = " + smartyResponse.getErrorMessage() : "" ) );
        }
    }

//    class SmartyUser
//    {
//        String userId = null;
//        String email = null;
//        String phone = null; 
//    }
}
