package com.fitbank.migrationdb.term;

import java.math.BigDecimal;
import java.sql.Date;
import java.util.HashMap;
import java.util.Map;

import org.hibernate.SQLQuery;
import org.hibernate.ScrollableResults;

import com.fitbank.balance.helper.BalanceData;
import com.fitbank.balance.helper.BalanceList;
import com.fitbank.balance.helper.TransactionBalance;
import com.fitbank.common.ApplicationDates;
import com.fitbank.common.Helper;
import com.fitbank.common.RequestData;
import com.fitbank.common.conectivity.HbSession;
import com.fitbank.common.helper.Constant;
import com.fitbank.common.helper.FormatDates;
import com.fitbank.common.logger.FitbankLogger;
import com.fitbank.dto.GeneralRequest;
import com.fitbank.dto.financial.FinancialRequest;
import com.fitbank.dto.management.Detail;
import com.fitbank.dto.management.Field;
import com.fitbank.fin.common.provision.GeneralProvision;
import com.fitbank.fin.helper.FinancialHelper;
import com.fitbank.fin.helper.TransactionData;
import com.fitbank.fin.helper.TransactionHelper;
import com.fitbank.hb.persistence.acco.Taccount;
import com.fitbank.hb.persistence.acco.loan.Tquotasaccount;
import com.fitbank.hb.persistence.acco.loan.TquotasaccountKey;
import com.fitbank.hb.persistence.fin.Tbalance;
import com.fitbank.migrationdb.common.MonitorThreads;

public class Provision extends Thread {

    private Date accountingDate;

    private Taccount taccount;
    private MonitorThreads mh;

    private Detail detail;

    public Provision(GeneralRequest pGeneralRequest, Taccount pTaccount,
            MonitorThreads pMh) throws Exception {
        detail = (Detail) pGeneralRequest;
        taccount = pTaccount;
        mh = pMh;
    }

    public void run() {
        try {
            Helper.setSession(HbSession.getInstance().openSession());
            this.procesar();
        } catch (Exception e) {
            FitbankLogger.getLogger().error(e.getMessage());
        } finally {
            try {
                mh.removeCounter();
            } catch (Exception e) {
                FitbankLogger.getLogger().error(e.getMessage());
            }
        }
    }

    private void procesar() throws Exception {
        Helper.beginTransaction();
        FitbankLogger.getLogger().error(
                "Cuenta : " + taccount.getPk().getCcuenta());
        detail.addField(new Field("ACCOUNTINGVALIDATOR"));
        RequestData.setSession(detail);
        accountingDate = FinancialHelper.getInstance().getAccountingdate(2, 0)
                .getFcontable();
        fillThreadLocal();
        try {
            FinancialRequest financialRequest = detail.toFinancialRequest();
            BalanceList<Tbalance> tbalances = TransactionBalance
                    .getBalanceData().getAccountBalance(
                            taccount.getPk().getCpersona_compania(),
                            taccount.getPk().getCcuenta(),
                            FormatDates.getDefaultExpiryDate());
            // Llena el resultset con las cuentas a calcular provisiones.
            ScrollableResults rSet = this.getSubAccounts(taccount);
            // Calcula provisiones por cada subcuenta.
            while (rSet.next()) {
                Object[] obj = rSet.get();
                Integer subacco = Integer.valueOf(((BigDecimal) obj[0])
                        .toString());
                BalanceList<Tbalance> tbalancesbysubacco = tbalances
                        .getBalanceBySubAccount(subacco, 0);
                try {
                    this.provisionBySubaccount(financialRequest,
                            tbalancesbysubacco);
                } catch (Exception e) {
                    FitbankLogger.getLogger().error(e.getMessage());
                }
                Helper.flushTransaction();
            }
            if (rSet != null) {
                rSet.close();
            }
        } finally {
            cleanThreadLocal();
        }
        Helper.commitTransaction();
    }

    private void provisionBySubaccount(FinancialRequest pFinancialRequest,
            BalanceList<Tbalance> pTbalanceBySubaccount) throws Exception {
        BalanceList<Tbalance> lprovisionbalance = pTbalanceBySubaccount
                .getProvisionBalance();
        for (Tbalance tbalance : lprovisionbalance) {
            TquotasaccountKey key = new TquotasaccountKey(tbalance.getPk()
                    .getCcuenta(), tbalance.getPk().getSubcuenta(), tbalance
                    .getPk().getSsubcuenta(), "299912",
                    ApplicationDates.DEFAULT_EXPIRY_TIMESTAMP, tbalance.getPk()
                            .getCpersona_compania());
            Tquotasaccount tquotasaccount = (Tquotasaccount) Helper.getBean(
                    Tquotasaccount.class, key);
            Map<String, BigDecimal> mQuotaValue = new HashMap<String, BigDecimal>();
            String key1 = tbalance.getPk().getSubcuenta() + "^IDEPP^2";
            mQuotaValue.put(key1, tquotasaccount.getInteres());
            pFinancialRequest.put("MQUOTAVALUE", mQuotaValue);
            pFinancialRequest.put("_accrualfrominstallmenttable", "Y");
            this.provisionByCategory(pFinancialRequest, tbalance);
        }
    }

    public void provisionByCategory(FinancialRequest pFinancialRequest,
            Tbalance pTbalance) throws Exception {
        Date pValuedate = pTbalance.getFinicio();
        BalanceList<Tbalance> lbalance = new BalanceList<Tbalance>();
        lbalance.add(pTbalance);
        if (TransactionHelper.getTransactionData().getAccountingdate() != null) {
            // Si se cambia la fecha contable de la cuenta, se toma la proxima
            // fecha contable para calcular provisiones
            // en el caso de que una cuota inicia una fecha no contable, y se
            // calula el proximo dia contable, en el
            // acumulado se alamcena del o de los dias anteriores a la proxima
            // fecha contable.
            pFinancialRequest.setAccountingDate(TransactionHelper
                    .getTransactionData().getAccountingdate());
        } else {
            TransactionHelper.getTransactionData().setAccountingdate(
                    accountingDate);
            pFinancialRequest.setAccountingDate(accountingDate);
        }
        pFinancialRequest.setValuedate(pValuedate);
        pFinancialRequest.setProcessdate(pValuedate);
        if (TransactionHelper.getTransactionData().getFinancialTransaction() != null) {
            TransactionHelper.getTransactionData().getFinancialTransaction()
                    .getFinancialRequest().setValuedate(pValuedate);
        }
        new GeneralProvision(lbalance, pFinancialRequest, true);
        TransactionHelper
                .getTransactionData()
                .getFinancialTransaction()
                .getFinancialRequest()
                .setValuedate(
                        FinancialHelper.getInstance().getNextAccountingdate(2,
                                0, accountingDate));
        TransactionHelper.getTransactionData().getFinancialTransaction()
                .saveProvisionBalances();
    }

    public void cleanThreadLocal() throws Exception {
        TransactionHelper.getTransactionData().clean();
        TransactionBalance.getBalanceData().clean();
    }

    public void fillThreadLocal() throws Exception {
        TransactionData transactionData = new TransactionData();
        BalanceData balanceData = new BalanceData();
        TransactionHelper.setTransactionData(transactionData);
        TransactionBalance.setBalanceData(balanceData);
    }

    private static final String SQL_PROVISION = "select sal.subcuenta from tsaldos sal "
            + "where sal.fhasta = :v_timestamp  and sal.particion =:vParticion "
            + "  and sal.ctiposaldocategoria = 'SAL' and sal.cestatuscuenta = '002' "
            + "  and sal.csubsistema = '05' and sal.cgrupobalance = '2' "
            + "  and sal.finicio <= :dateto  and sal.fvencimiento >= :dateto "
            + "  and sal.ccuenta = :account and sal.cpersona_compania = :company";

    private ScrollableResults getSubAccounts(Taccount taccount)
            throws Exception {
        SQLQuery sql = Helper.getSession().createSQLQuery(SQL_PROVISION);
        sql.setDate("dateto", accountingDate);
        sql.setTimestamp("v_timestamp",
                ApplicationDates.DEFAULT_EXPIRY_TIMESTAMP);
        sql.setString("vParticion", Constant.BD_EXPIRE_PARTITION);
        sql.setString("account", taccount.getPk().getCcuenta());
        sql.setInteger("company", taccount.getPk().getCpersona_compania());
        sql.setReadOnly(true);
        return sql.scroll();
    }
}
