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.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();
        
        // 
        try( 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();
        
        try( 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;
            }
            // определяем тип услуги: основной пакет, дополнительный пакет, подписка, услуга
            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-времени
        try ( BalanceUtils balanceUtils = new BalanceUtils( context.getConnection() ) )
        {
            // (обяз.) Сумма на счету абонента, указанная в копейках без каких-либо разделителей. Имеет знак «минус», если баланс отрицательный. Строковое значение
            outData.put( "balance", String.valueOf( balanceUtils.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();
        
        try( ProductDao productDao = new ProductDao( con );
             ProductSpecDao productSpecDao = new ProductSpecDao( 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 = Product.builder()
                    .setAccountId( tvAccount.getId() )
                    .setContractId( tvAccount.getContractId() )
                    .setProductSpecId( productSpec.getId() )
                    .setComment( "" )
                    .setDescription( "" )
                    .setActivationModeId( productSpec.getActivationModeList().get( 0 ).getId() )
                    .setUserId( User.USER_SERVER )
                    .build();

                // сначала считаем сколько надо денег на подключение с учетом остактов при оключении услуг
                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 ) );
                    }
                }

                try ( BalanceUtils balanceUtils = new BalanceUtils( context.getConnection() ) )
                {
                    int contractId = tvAccount.getContractId();
                    if ( balanceUtils.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 );
        
        try( BalanceUtils balanceUtils = new BalanceUtils( context.getConnection() ) )
        {
            Date now = new Date();
            int contractId = tvAccount.getContractId();
            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();
        
        try( ProductDao productDao = new ProductDao( con );
             ProductSpecDao productSpecDao = new ProductSpecDao( 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( "wink.base.id", 0 ) == parentId )
        {
            return WinkServiceType.BASE;
        }
        else if ( moduleSetup.getInt( "wink.ext.id", 0 ) == parentId )
        {
            return WinkServiceType.EXT;
        }
        else if ( moduleSetup.getInt( "wink.subs.id", 0 ) == parentId )
        {
            return WinkServiceType.SUBS;
        }
        else if ( moduleSetup.getInt( "wink.serv.id", 0 ) == parentId )
        {
            return WinkServiceType.SERV;
        }
        return WinkServiceType.UNKNOWN;
    }
}