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

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Connection;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;

import ru.bitel.bgbilling.kernel.container.managed.ServerContext;
import ru.bitel.bgbilling.kernel.contract.balance.common.bean.Charge;
import ru.bitel.bgbilling.kernel.contract.balance.server.bean.ChargeDao;
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.service.TvAccountService;
import ru.bitel.bgbilling.modules.tv.server.bean.TvAccountDao;
import ru.bitel.bgbilling.modules.tv.server.bean.enums.ModuleSetupKeys;
import ru.bitel.bgbilling.server.util.ModuleSetup;
import ru.bitel.bgbilling.server.util.Setup;
import ru.bitel.common.TimeUtils;
import ru.bitel.common.Utils;
import ru.bitel.oss.systems.inventory.product.common.bean.Product;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductSpec;
import ru.bitel.oss.systems.inventory.product.common.service.ProductService;
import ru.bitel.oss.systems.inventory.product.server.bean.ProductDao;
import ru.bitel.oss.systems.inventory.product.server.bean.ProductSpecDao;
import ru.bitel.oss.systems.order.product.common.service.ProductOrderService;

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

    private final WinkConf conf;
    private final String pathInfo;
    private final String[] path;
    private final ServerContext context;
    private final HttpServletRequest request;
    private final HttpServletResponse response;
    
    protected JSONObject outData = new JSONObject();
    
    public WinkService( ServerContext context, WinkConf conf, String pathInfo, HttpServletRequest request, HttpServletResponse response )
    {
        this.conf = conf;
        this.context = context;
        this.request = request;
        this.response = response;
        this.pathInfo = pathInfo;

        path = pathInfo.split( "/" );
    }

    public void handle()
        throws Exception
    {
        // /tv-ws/rest/{moduleId}/wink
        if ( path.length < 5 )
        {
            String message = "wink/...?";
            logger.error( message );
            response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message );
            return;
        }
        if ( !"account".equals( path[4] ) )
        {
            String message = "/account not found...";
            logger.error( message );
            response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message );
            return;
        }
        String accountId = path.length > 5 ? path[5] : null;
        String serviceId = path.length > 7 ? path[7] : null;
        TvAccount tvAccount = null;
        if ( accountId != null )
        {
            TvAccountDao tvAccountDao = new TvAccountDao( context.getConnection(), context.getModuleId() );
            tvAccount = tvAccountDao.get( accountId, new Date() );
            if ( tvAccount == null )
            {
                outData.put( "code", 1 );                    
                outData.put( "message", "Аккаунт на найден" );
                response.getWriter().write( outData.toString() + "\n" );
                return;
            }
        }
        String method = request.getMethod().toUpperCase();
        switch ( method )
        {
        // GET /account/{accountId}/service/{serviceId}/activation-status
        // GET /account/{accountId}/service/{serviceId}/deactivation-status
        // GET /account/{accountId}?san={san}
        case "GET":

            if ( path.length > 8 )
            {
                if ( path[8].equals( "activation-status" ) )
                {
                    doActivationStatus( tvAccount, serviceId );
                }
                else if ( path[8].equals( "deactivation-status" ) )
                {
                    doDeactivationStatus( tvAccount, serviceId );
                }
                else
                {
                    doError();
                    return;
                }
            }
            else if ( path.length == 6 )
            {
                doAccountInfo( tvAccount );
            }
            else
            {
                doError();
                return;
            }
            break;

        // PUT /account/{accountId}/service/{serviceId}
        case "PUT":

            if ( path.length == 8 )
            {
                doServiceOrder( tvAccount, serviceId );
            }
            else
            {
                doError();
                return;
            }
            break;

        // POST /account/{accountId}/service/{serviceId} 
        // POST /account/{accountId}/service/{serviceId}/request
        case "POST":

            if ( path.length > 8 && path[8].equals( "request" ) )
            {
                doAddOrderNotification( tvAccount, serviceId );
            }
            else if ( path.length == 8 )
            {
                doBuySingleService( tvAccount, serviceId );
            }
            else
            {
                doError();
                return;
            }
            break;

        // DELETE /account/{accountId}/service/{serviceId}/request
        case "DELETE":

            if ( path.length > 8 && path[8].equals( "request" ) )
            {
                doCancelOrderNotification( tvAccount, serviceId );
            }
            else
            {
                doError();
                return;
            }
            
            break;

        // PATCH /account/{accountId}
        case "PATCH":

            if ( path.length == 6 )
            {
                doAccountActivationNotification( tvAccount );
            }
            else
            {
                doError();
                return;
            }
            break;

        default:
            
            doError();
            return;
        }
        
        response.getWriter().write( outData.toString() );
    }
    
    /**
     * Проверка возможности подключения периодической услуги
     * Метод предназначен для проверки возможности подключения периодической услуги (используется только для STB Wink): 
     * • доступность условий по времени и территории подключения, 
     * • взаимоисключения и несовместимости на стороне АИС. 
     * Метод должен вызываться перед отправкой заявки на подключение периодической услуги. 
     * @param accountId
     * @param serviceId
     */
    protected void doActivationStatus( TvAccount tvAccount, String serviceId )
        throws Exception
    {
        logger.debug( "doActivationStatus" );

        final int moduleId = context.getModuleId();
        Connection con = context.getConnection();
        ProductDao productDao = new ProductDao( con );
        ProductSpecDao productSpecDao = new ProductSpecDao( con );

        // получаем список текущих услуг на аккаунте
        List<Product> productList = productDao.list( moduleId, tvAccount.getContractId(), tvAccount.getId(), false, new Date() );
        // продукт который хотят подключить
        ProductSpec productSpec = productSpecDao.getByIdentifier( serviceId, moduleId );
        if ( productSpec == null )
        {
            outData.put( "code", 1 );
            outData.put( "message", "Услуга #" + serviceId + " не найдена в БД" );
            return;
        }
        JSONObject data = new JSONObject( productSpec.getData() ).optJSONObject( "incompatible" );
        if ( data != null )
        {
            String executionType = "today";
            LocalDate date = LocalDate.now();
            LocalDate dateNextMonth = LocalDate.now().plusMonths( 1 ).withDayOfMonth( 1 );
            JSONArray activation = new JSONArray();
            JSONArray deactivation = new JSONArray();

            // проверка на не совместимость, если подключаемая услуга не совместима с какой нибудь из действующих
            // выдаем ошибку, клиент должен сначала отключить не совместимую услугу
            for ( Product product : productList )
            {
                ProductSpec checkProductSpec = productSpecDao.get( product.getProductSpecId() );
                String value = data.optString( String.valueOf( product.getProductSpecId() ) );
                if ( "0".equals( value ) )
                {
                    outData.put( "code", 1 );
                    outData.put( "message", "Услуга не совместима с услугой: " + product.getProductSpecTitle() );
                    return;
                }
                else if ( "1".equals( value ) )
                {
                    activation.put( productSpec.getIdentifier() );
                }
                else if ( "-1".equals( value ) )
                {
                    activation.put( productSpec.getIdentifier() );
                    deactivation.put( checkProductSpec.getIdentifier() );
                }
                else if ( "-2".equals( value ) )
                {
                    activation.put( productSpec.getIdentifier() );
                    deactivation.put( checkProductSpec.getIdentifier() );
                    executionType = "billing_cycle";
                    date = dateNextMonth;
                }
            }
            
            // activationServicesList Список подключаемых услуг "activationServicesList" : ["svc1", "svc2"],
            outData.put( "activationServicesList", activation );
            // 
            // deactivationServicesList Список отключаемых взаимоисключающих услуг "deactivationServicesList" : ["svc3", "svc4"]
            outData.put( "deactivationServicesList", deactivation );
            // executionType string да Дата подключения услуги. Возможные значения: 
            // `today` — в день подачи заявки; 
            // `tomorrow` — на следующий день после подачи заявки; 
            // `billing_cycle` — в начале расчетного периода; 
            // `on_date` — в определённый день месяца, если ни одно из ранее перечисленных значений не подходит; 
            // `unknown` — нет сведений о дате выполнения.
            outData.put( "executionType", executionType );
            // expectedDate string да Плановая дата подключения услуги в формате mm/dd/yyyy
            outData.put( "expectedDate", TimeUtils.format( date, "MM/dd/yyyy" ) );
        }
        
        outData.put( "code", 0 ); // (обяз.) Код ошибки
    }
    
    /**
     * Проверка возможности отключения периодической услуги 
     * @param accountId
     * @param serviceId
     * @throws Exception 
     */
    protected void doDeactivationStatus( TvAccount tvAccount, String serviceId )
        throws Exception
    {
        logger.debug( "doDeactivationStatus" );

        final int moduleId = context.getModuleId();
        Connection con = context.getConnection();
        
        ProductSpecDao productSpecDao = new ProductSpecDao( con );
        // получаем список текущих услуг на аккаунте
        //List<Product> productList = productDao.list( moduleId, tvAccount.getContractId(), tvAccount.getId(), false, new Date() );
        // получаем настройки отключаемого пакета
        ProductSpec productSpec = productSpecDao.getByIdentifier( serviceId, moduleId );
        if ( productSpec == null )
        {
            outData.put( "code", 1 );
            outData.put( "message", "Услуга #" + serviceId + " не найдена в БД" );
            return;
        }
        // определяем тип услуги: основной пакет, дополнительный пакет, подписка, услуга
        WinkServiceType winkServiceType = getWinkServiceType( moduleId, productSpec.getParentId() );
        if ( winkServiceType == WinkServiceType.UNKNOWN )
        {
            outData.put( "code", 1 );
            outData.put( "message", "Ошибка настройки типа услуги." );
            return;
        }
        // вроде отключить нельзя только основной пакет, про остальные не понятно
        if ( winkServiceType == WinkServiceType.BASE )
        {
            outData.put( "code", 1 );
            outData.put( "message", "Основной пакет отключить нельзя." );
            return;
        }
        
        String executionType = "on_date";
        LocalDate date = LocalDate.now();
        JSONArray activation = new JSONArray();
        JSONArray deactivation = new JSONArray();
        deactivation.put( productSpec.getIdentifier() );
        date = date.plusMonths( 1 ).withDayOfMonth( 1 );

        // activationServicesList Список подключаемых услуг "activationServicesList" : ["svc1", "svc2"],
        outData.put( "activationServicesList", activation );
        // 
        // deactivationServicesList Список отключаемых взаимоисключающих услуг "deactivationServicesList" : ["svc3", "svc4"]
        outData.put( "deactivationServicesList", deactivation );
        // executionType string да Дата подключения услуги. Возможные значения: 
        // `today` — в день подачи заявки; 
        // `tomorrow` — на следующий день после подачи заявки; 
        // `billing_cycle` — в начале расчетного периода; 
        // `on_date` — в определённый день месяца, если ни одно из ранее перечисленных значений не подходит; 
        // `unknown` — нет сведений о дате выполнения.
        outData.put( "executionType", executionType );
        // expectedDate string да Плановая дата подключения услуги в формате mm/dd/yyyy
        outData.put( "expectedDate", TimeUtils.format( date, "MM/dd/yyyy" ) );

        outData.put( "code", 0 ); // (обяз.) Код ошибки
    }

    /**
     * Получение информации о счете
     * @param accountId
     * @throws Exception 
     */
    protected void doAccountInfo( TvAccount tvAccount )
        throws Exception
    {
        logger.debug( "doAccountInfo" );
        
        outData.put( "code", 0 ); // (обяз.) Код ошибки
        //outData.put( "name", "" ); // (не обяз.) Имя абонента без Фамили
        //outData.put( "creationDate", "" ); // (не обяз.) Дата создания учётной записи в формате UNIX-времени
        // (обяз.) Сумма на счету абонента, указанная в копейках без каких-либо разделителей. Имеет знак «минус», если баланс отрицательный. Строковое значение
        outData.put( "balance", String.valueOf( new BalanceUtils( context.getConnection() ).getBalance( LocalDate.now(), tvAccount.getContractId() ).multiply( new BigDecimal( 100 ) ).setScale( 0, RoundingMode.HALF_EVEN ).longValue() ) );
        // outData.put( "tariffName", "" ); // (не обяз.) Наименование тарифа
        // outData.put( "charge", "" ); // (не обяз.) Резервный параметр
    }

    /**
     * Заявка на подключение или отключение периодической услуги (версия 2) 
     * @param tvAccount
     * @param serviceId
     */
    protected void doServiceOrder( TvAccount tvAccount, String serviceId )
        throws Exception
    {
        logger.debug( "doServiceOrder" );

        JSONObject inParam = getRequestData();
        String operationType = inParam.optString( "operationType" );
        logger.debug( "operationType = " + operationType );
        
        LocalDate date = LocalDate.now();
        final int moduleId = context.getModuleId();
        Connection con = context.getConnection();
        
        ProductSpecDao productSpecDao = new ProductSpecDao( con );

        ProductDao productDao = new ProductDao( con );
        // получаем настройки запрашиваемой услуги
        ProductSpec productSpec = productSpecDao.getByIdentifier( serviceId, moduleId );
        if ( productSpec == null )
        {
            outData.put( "code", 1 );
            outData.put( "message", "Услуга #" + serviceId + " не найдена в БД" );
            return;
        }

        JSONObject data = new JSONObject( productSpec.getData() ).optJSONObject( "incompatible" );
        List<Product> productList = productDao.list( moduleId, tvAccount.getContractId(), tvAccount.getId(), false, new Date() );

        // тип операции
        // - signon — подключение
        if ( "signon".equals( operationType ) )
        {
            logger.debug( "signon" );
            
            Product newProduct = new Product()
                .setAccountId( tvAccount.getId() )
                .setContractId( tvAccount.getContractId() )
                .setProductSpecId( productSpec.getId() )
                .setComment( "" )
                .setDescription( "" )
                .setActivationModeId( productSpec.getActivationModeList().get( 0 ).getId() )
                .setUserId( User.USER_SERVER );

            // сначала считаем сколько надо денег на подключение с учетом остактов при оключении услуг
            BigDecimal needSum = BigDecimal.ZERO;
            for ( Product product : productList )
            {
                ProductSpec checkProductSpec = productSpecDao.get( product.getProductSpecId() );
                String value = data.optString( String.valueOf( product.getProductSpecId() ) );
                logger.debug( "value = " + value );
                if ( "1".equals( value ) )
                {
                    needSum = needSum.add( new JSONObject( productSpec.getData() ).optBigDecimal( "cost", BigDecimal.ZERO ) );
                }
                else if ( "-1".equals( value ) )
                {
                    // расчет суммы не израсходованной в случае досрочного прекращения продукта
                    Date currentDate = new Date();
                    long all = ( product.getTimeTo().getTime() - product.getTimeFrom().getTime() ) / 1000;
                    long after = ( product.getTimeTo().getTime() - currentDate.getTime() ) / 1000;
                    BigDecimal currentProductSum = new JSONObject( checkProductSpec.getData() ).optBigDecimal( "cost", BigDecimal.ZERO );
                    BigDecimal secCost = currentProductSum.divide( new BigDecimal( all ), 10, RoundingMode.HALF_UP );
                    BigDecimal sum = secCost.multiply( new BigDecimal( after ) ).setScale( 2, RoundingMode.HALF_UP );
                    needSum = needSum.add( new JSONObject( productSpec.getData() ).optBigDecimal( "cost", BigDecimal.ZERO ) ).subtract( sum );
                }
                else if ( "-2".equals( value ) )
                {
                    needSum = needSum.add( new JSONObject( productSpec.getData() ).optBigDecimal( "cost", BigDecimal.ZERO ) );
                }
            }

            int contractId = tvAccount.getContractId();
            if ( new BalanceUtils( context.getConnection() ).getBalance( LocalDate.now(), contractId ).compareTo( needSum ) < 0 )
            {
                outData.put( "code", 1 );
                outData.put( "message", "На балансе не достаточно средств" );
                return;
            }
            
            boolean productActivate = false;
            ProductOrderService productOrderService = context.getService( ProductOrderService.class, moduleId );
            for ( Product product : productList )
            {
                String value = data.optString( String.valueOf( product.getProductSpecId() ) );
                logger.debug( "value = " + value );
                if ( "-2".equals( value ) )
                {
                    date = date.plusMonths( 1 ).withDayOfMonth( 1 );
                    productOrderService.productDeactivate( tvAccount.getContractId(), product.getId(), null, false, true, false );

                    Date time = TimeUtils.convertLocalDateToDate( date );
                    newProduct.setTimeFrom( time );
                    productOrderService.productActivate( newProduct, time, false, true );
                    productActivate = true;
                }
                else if ( "-1".equals( value ) )
                {
                    LocalDateTime dateTime = LocalDateTime.now().plusSeconds( 60 );
                    productOrderService.productDeactivate( tvAccount.getContractId(), product.getId(), TimeUtils.convertLocalDateTimeToDate( dateTime ), false, true, true );

                    Date time = TimeUtils.convertLocalDateTimeToDate( dateTime.plusSeconds( 1 ) );
                    newProduct.setTimeFrom( time );
                    productOrderService.productActivate( newProduct, time, false, true );
                    productActivate = true;
                }
            }
            if ( !productActivate )
            {
                logger.debug( "productActivate = " + productActivate );
                Date time = TimeUtils.convertLocalDateTimeToDate( LocalDateTime.now() );
                logger.debug( "time = " + time );
                newProduct.setTimeFrom( time );
                context.getService( ProductOrderService.class, moduleId ).productActivate( newProduct, time, false, true );
            }
        }
        // - signoff — отключение
        else if ( "signoff".equals( operationType ) )
        {
            logger.debug( "signoff" );
            date = date.plusMonths( 1 ).withDayOfMonth( 1 );
            for ( Product product : productList )
            {
                ProductSpec checkProductSpec = productSpecDao.get( product.getProductSpecId() );
                if ( checkProductSpec.getIdentifier().equals( serviceId ) )
                {
                    Date closeDate = TimeUtils.convertLocalDateTimeToDate( date.atStartOfDay().minusSeconds( 1 ) ); 
                    product.setTimeTo( closeDate );
                    product.setDeactivationTime( closeDate );
                    context.getService( ProductService.class, moduleId ).productUpdate( product );
                    break;
                }
            }
        }

        // executionType string да Дата подключения услуги. Возможные значения: 
        // `today` — в день подачи заявки; 
        // `tomorrow` — на следующий день после подачи заявки; 
        // `billing_cycle` — в начале расчетного периода; 
        // `on_date` — в определённый день месяца, если ни одно из ранее перечисленных значений не подходит; 
        // `unknown` — нет сведений о дате выполнения.
        outData.put( "executionType", "on_date" );
        // expectedDate string да Плановая дата подключения услуги в формате mm/dd/yyyy
        outData.put( "expectedDate", TimeUtils.format( date, "MM/dd/yyyy" ) );

        outData.put( "code", 0 ); // (обяз.) Код ошибки
    }
    
    /**
     * Запрос на покупку разовой услуги (версия 2)
     * Метод предназначенный для покупки разовой услуги. 
     * При успешном выполнении запроса стоимость услуги (контента) с учётом НДС списывается с лицевого счета пользователя в АИС. 
     * @param tvAccount
     * @param serviceId
     * @throws Exception 
     */
    protected void doBuySingleService( TvAccount tvAccount, String serviceId )
        throws Exception
    {
        logger.debug( "doBuySingleService" );
        
        JSONObject inParam = getRequestData();
        // accountId string да идентификатор учетной записи в АИС Партнёра
        // String accountId = inParam.optString( "accountId" );
        // serviceId string да внешний идентификатор услуги
        // String serviceId = inParam.optString( "serviceId" );
        // description string да имя контента/разовой услуги
        String description = inParam.optString( "description" );
        // price string да текущая стоимость услуги (контента) с учётом НДС. Значение в копейках без разделителя
        BigDecimal price = new BigDecimal( inParam.optString( "price" ) ).divide( new BigDecimal( 100 ), 2, RoundingMode.HALF_EVEN );
        
        Date now = new Date();
        int contractId = tvAccount.getContractId();
        BalanceUtils balanceUtils = new BalanceUtils( context.getConnection() );
        if ( balanceUtils.getBalance( LocalDate.now(), contractId ).compareTo( price ) >= 0 )
        {
            Charge charge = new Charge( 0, User.USER_SERVER, tvAccount.getContractId(), conf.chargeTypeId, new Date(), description, price, new Date() );
            new ChargeDao( context.getConnection() ).update( charge );
            balanceUtils.updateBalance( now, contractId );
            outData.put( "code", 0 );
            outData.put( "transactionId", String.valueOf( charge.getId() ) );
        }
        else
        {
            outData.put( "code", 1 );
            outData.put( "message", "На балансе не достаточно средств" );
        }
    }
    
    /**
     * Уведомление об активации учетной записи 
     * @param tvAccount
     */
    protected void doAccountActivationNotification( TvAccount tvAccount )
    {
        logger.debug( "doAccountActivationNotification" );

        final int moduleId = context.getModuleId();
        Connection con = context.getConnection();

        try
        {
            ProductDao productDao = new ProductDao( con );
            outData.put( "code", 0 );
            final int contractId = tvAccount.getContractId();
            List<Product> products = productDao.list( moduleId, contractId, tvAccount.getId(), false, null, null, null, null, true, true );
            for ( Product product : products )
            {
                if ( product.getActivationTime() == null )
                {
                    try
                    {
                        context.getService( ProductOrderService.class, moduleId ).productActivate( product, new Date(), true, false );
                    }
                    catch (Exception e)
                    {
                        tvAccount.setStatus( TvAccount.STATUS_LOCKED );
                        context.getService( TvAccountService.class, moduleId ).tvAccountUpdate( contractId, tvAccount, false, false, 500L );
                    }
                }
            }
        }
        catch (Exception e) 
        {
            outData.put( "code", 2 );
            outData.put( "message", e.getMessage() );
        }
    }
    
    /**
     * Создания отложенной заявки на подключение/отключение услуг.
     * @param tvAccount
     * @param serviceId
     * @throws Exception
     */
    protected void doAddOrderNotification( TvAccount tvAccount, String serviceId )
        throws Exception
    {
        logger.debug( "doAddOrderNotification" );
        //
        outData.put( "code", 0 );
    }    
    
    /**
     * Уведомление об отмене отложенной заявки 
     * @param tvAccount
     * @param serviceId
     */
    protected void doCancelOrderNotification( TvAccount tvAccount, String serviceId )
        throws Exception
    {
        logger.debug( "doCancelOrderNotification" );

        final int moduleId = context.getModuleId();
        Connection con = context.getConnection();
        
        ProductSpecDao productSpecDao = new ProductSpecDao( con );

        ProductDao productDao = new ProductDao( con );
        // получаем настройки запрашиваемой услуги
        ProductSpec productSpec = productSpecDao.getByIdentifier( serviceId, moduleId );
        if ( productSpec == null )
        {
            outData.put( "code", 1 );
            outData.put( "message", "Услуга #" + serviceId + " не найдена в БД" );
            return;
        }

        final Date now = new Date();
        final int contractId = tvAccount.getContractId();
        List<Product> products = productDao.list( moduleId, contractId, tvAccount.getId(), false, null, null, null, null, true, true );

        ProductService productService = context.getService( ProductService.class, moduleId );
        for ( Product product : products )
        {
            if ( product.getTimeFrom().after( now ) )
            {
                productService.productDelete( contractId, product.getId() );
            }
            
            if ( product.getDeactivationTime().after( now ) )
            {
                product.setTimeTo( null );
                product.setDeactivationTime( null );
                productService.productUpdate( product );
            }
        }
        //
        outData.put( "code", 0 );
    }
    
    /**
     * 
     * @throws IOException
     */
    protected void doError()
        throws IOException
    {
        String message = pathInfo + " not found...";
        logger.error( message );
        response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message );
    }
    
    private JSONObject getRequestData()
        throws Exception
    {
        return new JSONObject( new String( Utils.readByBlock( request.getInputStream() ), "UTF-8" ) );
    }
    
    private WinkServiceType getWinkServiceType( int moduleId, int parentId )
    {
        ModuleSetup moduleSetup = Setup.getSetup().getModuleSetup( moduleId );
        if ( moduleSetup.getInt( ModuleSetupKeys.WINK_BASE_ID ) == parentId )
        {
            return WinkServiceType.BASE;
        }
        else if ( moduleSetup.getInt( ModuleSetupKeys.WINK_EXT_ID ) == parentId )
        {
            return WinkServiceType.EXT;
        }
        else if ( moduleSetup.getInt( ModuleSetupKeys.WINK_SUBS_ID ) == parentId )
        {
            return WinkServiceType.SUBS;
        }
        else if ( moduleSetup.getInt( ModuleSetupKeys.WINK_SERV_ID ) == parentId )
        {
            return WinkServiceType.SERV;
        }
        return WinkServiceType.UNKNOWN;
    }
}