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

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

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

import ru.bitel.bgbilling.apps.tv.access.om.AbstractOrderEvent;
import ru.bitel.bgbilling.apps.tv.access.om.AccountOrderEvent;
import ru.bitel.bgbilling.apps.tv.access.om.ProductOrderEvent;
import ru.bitel.bgbilling.kernel.container.managed.ServerContext;
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.bgbilling.modules.tv.dyn.JsonClient;
import ru.bitel.bgbilling.modules.tv.dyn.JsonClient.JsonClientException;
import ru.bitel.bgbilling.modules.tv.server.bean.TvAccountDao;
import ru.bitel.common.ParameterMap;
import ru.bitel.common.Utils;
import ru.bitel.oss.systems.inventory.product.common.bean.ProductSpec;

/**
 * http://stand.netup.tv/downloads/mw-api.html
 * абонент авторизируется по логин/пасс
 * @author dimon
 */
public class NetUpOrderManager
    implements OrderManager
{
    private static final Logger logger = LogManager.getLogger();
    
    private JsonClient jsonClient;

    private NetUpConf conf;
 
    private int moduleId;
    private TvAccountDao tvAccountDao;
    
    private Map<String, String> requestOptions = new HashMap<>();

    @Override
    public Object init(ServerContext ctx, int moduleId, TvDevice tvDevice, TvDeviceType tvDeviceType, ParameterMap config) throws Exception
    {
        this.moduleId = moduleId;
        this.conf = ctx.getSetup().getConfig( moduleId, NetUpConf.class );
        this.conf.fill(config);
        this.requestOptions.put( "Content-Type", "application/json" );
        this.requestOptions.put( "Accept", "application/json" );
        return null;
    }

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

    @Override
    public Object connect(ServerContext ctx) throws Exception
    {
        jsonClient = new JsonClient( new URL( conf.providerURL ), null, null );
        tvAccountDao = new TvAccountDao( ctx.getConnection(), moduleId );
        return null;
    }

    @Override
    public Object disconnect(ServerContext ctx) throws Exception
    {
        if ( jsonClient != null )
        {
            jsonClient.disconnect();
            jsonClient = null;
        }

        if ( tvAccountDao != null )
        {
            tvAccountDao.recycle();
            tvAccountDao = null;
        }

        return null;
    }

    @Override
    public Object accountCreate(AccountOrderEvent accountOrderEvent, ServerContext serverContext) throws Exception
    {
        // вызов: создание аккаунта
        final TvAccount tvAccount = accountOrderEvent.getNewTvAccount();
        
        String login = tvAccount.getLogin();
        String password = tvAccount.getPassword();
        
        logger.info("NetUpOrderManager.accountCreate login=" + login);
        
        JSONObject account = new JSONObject();
        account.put( "login", login );
        account.put( "password", password );
        
        try
        {
            Object response = jsonClient.invokeAndGetObject( JsonClient.Method.post, requestOptions, conf.providerURL + "/user", null, account );
            // >> {"password":"653129","login":"10000003"}
            // << 5
            // возвращается просто число - access card
            if( response != null && response instanceof Integer )
            {
                String access_card = response.toString();
                accountOrderEvent.getEntry().setDeviceAccountId(access_card);
                
                //tvAccount.setIdentifier( access_card );
                //tvAccountDao.update( tvAccount );
                //serverContext.publishAfterCommit( new TvAccountModifiedEvent( moduleId, accountOrderEvent.getContractId(), 0, child, newChild ) );
            }
            else
            {
                logger.error("accountCreate: unknown response: " + response);
                return false;
            }
        }
        catch( JsonClientException ex )
        {
            // Response code = 500
            // {"error":"Failed to insert record in customers"}
            if( ex.getResponseCode() == 500 && Utils.maskNull(ex.getData()).contains("record in customers") )
            {
                logger.error( "accountCreate: duplicate login? response 500 : " + ex.getData() );
                return false;
            }
            throw ex;
        }
        
        return null;
    }

    private static String ACC_STATE_BLOCKED = "blocked";
    private static String ACC_STATE_ACTIVE = "active";
    
    @Override
    public Object accountModify(AccountOrderEvent accountOrderEvent, ServerContext serverContext) throws Exception
    {
        // вызов: редактирование аккаунта, в т.ч. внутри смена статуса аккаунта
        // вызов: сдвиг дат при редактировании на актуальные (при предыдущем редактировании могло быть вызвано удаление! см. accountRemove)
        final TvAccount tvAccount = accountOrderEvent.getNewTvAccount();
        final String access_card = tvAccount.getDeviceAccountId();
        
        logger.info("NetUpOrderManager.accountModify login=" + tvAccount.getLogin());
        
        if(Utils.isBlankString(access_card))
        {
            logger.error( "accountModify: access_card for account {id="+tvAccount.getId()+", login=" + tvAccount.getLogin() + "} is empty" );
            return false;
        }
        
        String state = switch (tvAccount.getStatus()) {
            case TvAccount.STATUS_CLOSED:
            case TvAccount.STATUS_LOCKED:
                yield ACC_STATE_BLOCKED;
            default:
                yield ACC_STATE_ACTIVE;
        };
        
        JSONObject account = new JSONObject();
        account.put( "state", state );
        
        /*JSONObject response =*/ jsonClient.invoke( JsonClient.Method.post, requestOptions, conf.providerURL + "/personal-account-block/" + access_card, null, account );
        // по протоколу ничего не возвращается, на деле - эхо
        // >> {"state":"blocked"}
        // << {"state":"blocked"}
        // Response code = 400
        // << {"error":"failed to parse URL parameter|name:access_card|raw_value:6_666"}
        
        // после этого надо ещё дропнуть подписку если заблочили, ибо mw нормально не работает тут, аккаунт
        // разблокируется сам после логина клиента туда(!), нетап отвечает следующее:
        // "Блокировка аккаунта - не означает блокировки услуг. Вы даете доступ к услугам и их и надо блокировать.
        // curl ***/access-media/34  -X POST -H "Content-Type: application/json" -d '{}'"
        // при разблокировке тоже синхронизируем (ну, при каждом редактировании аккаунта получается)
        if (!productsModifySyncFull(accountOrderEvent, serverContext, access_card, state.equals(ACC_STATE_ACTIVE) ))
        {
            logger.error( "accountModify: not succes productsModifySyncFull" );
            return false;
        }
        
        return null;
    }

    @Override
    public Object accountRemove(AccountOrderEvent accountOrderEvent, ServerContext serverContext) throws Exception
    {
        // вызов: сдвиг дат при редактировании на неактуальные. фактическое удаление (даёт только при неактульном периоде)
        final TvAccount tvAccount = accountOrderEvent.getOldTvAccount();
        final String access_card = tvAccount.getDeviceAccountId();
        
        logger.info("NetUpOrderManager.accountRemove login=" + tvAccount.getLogin());
       
        if(Utils.isBlankString(access_card))
        {
            logger.error( "accountRemove: access_card for account {id="+tvAccount.getId()+", login=" + tvAccount.getLogin() + "} is empty" );
            return false;
        }
            
        try
        {
            Object response = jsonClient.invokeAndGetObject( JsonClient.Method.delete, requestOptions, conf.providerURL + "/user/" + access_card, null, null );
            // по протоколу возвращается значение: true
            // но не получилось до сюда дойти, всегда 500
            logger.info("response=" + response); 
            
            if( response == null || !(response instanceof Boolean) )
            {            
                logger.error("accountRemove: unknown response: " + response);
                return false;
            }
        }
        catch( JsonClientException ex )
        {
            // Response code = 500
            // << {"error":"Failed to close personal account"}
            if( ex.getResponseCode() == 500 && Utils.maskNull(ex.getData()).contains("close personal") )
            {
                logger.error( "accountRemove: error close account? response 500 : " + ex.getData() );
                return false;
            }
            throw ex;
        }
        
        return null;
    }

    @Override
    public Object accountStateModify(AccountOrderEvent accountOrderEvent, ServerContext ctx) throws Exception
    {
        logger.info("NetUpOrderManager.accountStateModify->accountModify");
        return accountModify( accountOrderEvent, ctx );
    }

    @Override
    public Object accountOptionsModify(AbstractOrderEvent e, ServerContext ctx) throws Exception
    {
        logger.info("NetUpOrderManager.accountOptionsModify->skip");
        return null;
    }

    @Override
    public Object productsModify(ProductOrderEvent productOrderEvent, ServerContext ctx) throws Exception
    {
        logger.info("NetUpOrderManager.productsModify");
        
        final TvAccount tvAccount = productOrderEvent.getTvAccount();
        if ( tvAccount == null )
        {
            logger.error( "productsModify: productOrderEvent.getTvAccount() = null" );
            return false;
        }

        final String access_card = tvAccount.getDeviceAccountId();
        if(Utils.isBlankString(access_card))
        {
            logger.error( "productsModify: access_card for account {id="+tvAccount.getId()+", login=" + tvAccount.getLogin() + "} is empty" );
            return false;
        }
        
        // помимо state проверяем и статус==активе, ибо если неактивный то слать надо пустую подписку (фишка такая, см. камент в accountModify)
        return productsModifySyncFull(productOrderEvent, ctx, access_card, tvAccount.getDeviceState() == TvAccount.STATE_ENABLE && tvAccount.getStatus() == TvAccount.STATUS_ACTIVE );
    }
    
    /**
     * Синхронизация продуктов из orderEvent на access_card Если accountEnabled=false то эта инфа не используется, а
     * только блочится подписка (отправляется пустая).
     */
    private boolean productsModifySyncFull( final AbstractOrderEvent orderEvent, final ServerContext serverContext, final String access_card, final boolean accountEnabled )
        throws Exception
    {
        logger.debug( "NetUpOrderManager.productsModifySyncFull access_card=" + access_card + ", accountEnabled=" + accountEnabled );
        
        /*
        по протоколу:
        POST http://mw.iptv/admin/access-media/{access_card}
        {
            "media_contents": [{"media_content_code": 0, "service_type": "tv", "till": 0}],
            "media_groups": [{"media_group_code": 0, "service_type": "tv", "till": 0}]
        }
        */

        JSONObject data = new JSONObject();

        if( accountEnabled )
        {
            JSONArray media_contents = new JSONArray();
            JSONArray media_groups = new JSONArray();

            // final Set<Long> packagesToAdd = TvDynUtils.getFullSetToEnable( e, o -> Utils.parseLong( o ), serviceMode, true );
            // final Set<Long> packagesToAdd = TvDynUtils.getFullSetToEnable2( e, o -> Utils.parseLong( o ), serviceMode, true );
            // список активных продуктов
            for( ProductSpec productSpec : orderEvent.getFullProductSpecSetToEnable() )
            {
                logger.info( "Product: " + productSpec );
                long code = Utils.parseLong( productSpec.getIdentifier().trim() );
                
                JSONObject spec = new JSONObject();
                spec.put("media_group_code", code);
                spec.put("service_type", "tv");
                // spec.put("till", 0);

                media_groups.put(spec);

                spec = new JSONObject();
                spec.put("media_group_code", code);
                spec.put("service_type", "tvod");
                // spec.put("till", 0);
                
                media_groups.put(spec);
            }

            data.put( "media_contents", media_contents );
            data.put( "media_groups", media_groups );
        }
        
        Object response = jsonClient.invokeAndGetObject( JsonClient.Method.post, requestOptions, conf.providerURL + "/access-media/" + access_card, null, data );
        
        if( response == null || !(response instanceof Boolean) )
        {            
            logger.error("productsModifySyncFull: unknown response: " + response);
            return false;
        }
        
        // >> {"media_groups":[{"service_type":"tv","till":0,"media_group_code":666}],"media_contents":[]}
        // << true
        // >> {"media_groups":[],"media_contents":[]}
        // << true
        
        return ((Boolean)response).booleanValue();
    }
}
