package com.fitbank.bpmserver;

import java.sql.Timestamp;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

import org.hibernate.LockMode;
import org.hibernate.SQLQuery;
import org.jbpm.api.ProcessInstance;
import org.jbpm.api.ProcessInstanceQuery;

import com.fitbank.bpm.client.BPMProcessor;
import com.fitbank.bpm.client.BPMProperties;
import com.fitbank.bpmserver.ServiceBpm;
import com.fitbank.common.ApplicationDates;
import com.fitbank.common.BeanManager;
import com.fitbank.common.exception.FitbankException;
import com.fitbank.common.Helper;
import com.fitbank.common.conectivity.HbSession;
import com.fitbank.common.hb.UtilHB;
import com.fitbank.dto.management.Detail;
import com.fitbank.dto.management.Field;
import com.fitbank.hb.persistence.gene.Tflowinstance;

public class TimeoutManager extends ServiceBpm {

    public TimeoutManager() {
        this.setName("TimeoutManager");
    }

    private long getTime(String pVal) {
        String time = pVal.replaceAll("minutes", " 60").replaceAll("minutos", " 60").replaceAll("hours", "3600")
                .replaceAll("horas", "3600");
        long ltime = 1;
        String[] vals = time.split(" ");
        for (String val : vals) {
            val = val.trim();
            try {
                ltime *= Long.valueOf(val);
            } catch (NumberFormatException e) {
                continue;
            }
        }
        return ltime * 1000;
    }

    @Override
    public void execute() {
        if (!BPMProperties.getConfig().getBoolean("fit.bpm.process")) {
            return;
        }

        LOGGER.debug("Ejecutando TimeoutManager para BPM cada " + this.getPeriod() + " minutos");
        Helper.setSession(HbSession.getInstance().openSession());
        this.manageFlows();
        Helper.closeSession();
        LOGGER.debug("Finalizado el timeout Manager para BPM");
    }

    private void manageFlows() {
        BPMProcessor cli = new BPMProcessor();
        ProcessInstanceQuery qry = cli.getExecutionService().createProcessInstanceQuery();
        List<ProcessInstance> lpi = qry.list();
        for (ProcessInstance pi : lpi) {
            try {
                if (pi.isEnded()) {
                    continue;
                }
                LOGGER.debug("Manejando el Flujo " + pi.getId());

                BPMProcessor c = BPMProcessor.findProcessInstanceById(pi.getId());
                if (c == null) {
                    LOGGER.warn("Flujo " + pi.getId() + " ya no existe en la base de conocimientos");
                    continue;
                }

                this.manageTimeout(c, pi.getId());
                Thread.sleep(100);
            } catch (Exception e) {
                LOGGER.error("Error desconocido", e);
            }
        }
    }

    private String findRealId(BPMProcessor c, String activity) {
        String sub = c.getPid();
        BPMProcessor a = BPMProcessor.findProcessInstanceById(sub);
        if (a == null) {
            LOGGER.warn("Flujo " + sub + " ya no existe en la base de conocimientos");
            return sub;
        }

        String act = activity;
        String aux = null;
        do {
            aux = a.findExecutionId(act);
            if (aux != null) {
                sub = aux;
                a = BPMProcessor.findProcessInstanceById(sub);
                if (a != null) {
                    act = a.findActualStates().iterator().next();
                }
            }
        } while (aux != null);
        return sub;
    }

    public Tflowinstance findInstance(String pID) {
        UtilHB util = new UtilHB("from com.fitbank.hb.persistence.gene.Tflowinstance o where o.nombreinstancia=:pid"
                + " and o.finalizado=:ended");
        util.setString("pid", pID);
        util.setString("ended", "0");
        return (Tflowinstance) util.getObject();
    }

    private void timeoutHistory(Detail detail, BPMProcessor c) {
        Integer max = obtainParameterNumber("MAX_NOTIFICACIONES",
                detail.getCompany());
        String userNotify = detail.findFieldByNameCreate("_USER_NOTIFY")
                .getStringValue();
        String dataToJSON = detail.findFieldByNameCreate("_AUTH_HISTORY")
                .getStringValue();
        JSONObject jsonHistoryTree = new JSONObject();
        int count = 0;
        if (dataToJSON != null) {
            jsonHistoryTree = (JSONObject) JSONSerializer.toJSON(dataToJSON);
        }

        if (userNotify != null && jsonHistoryTree.get(userNotify) == null) {
            jsonHistoryTree.put(userNotify, count);
        }

        if (userNotify != null) {
            count = jsonHistoryTree.getInt(userNotify);
            if (count <= max) {
                ++count;
            }
            jsonHistoryTree.put(userNotify, count);
            LOGGER.debug("History TimeoutManager " + jsonHistoryTree.toString()
                    + " -pid-" + c.getPid());
            c.setVariable("detail", detail);
            detail.findFieldByNameCreate("_AUTH_HISTORY").setValue(
                    jsonHistoryTree.toString());
        }
    }

    private boolean validateTimeout(Detail detail, String time, String pid, String sub) {
        Field fTime = detail.findFieldByNameCreate("_TIME_AUTH");
        if (fTime.getValue() != null) {
            time = fTime.getStringValue();
        }
        Object val = detail.findFieldByNameCreate("_NOTIFY_TIME").getValue();
        LOGGER.debug("_TIME_AUTH: " + time + "; _NOTIFY_TIME: " + val);
        if (val == null) {
            return false;
        }
        long ltime = this.getTime(time);
        long last = (BeanManager.convertObject(val, Timestamp.class)).getTime();
        long now = ApplicationDates.getDBTimestamp().getTime();
        LOGGER.debug("Timeout Flow: " + pid + " " + sub + " " + ltime + " " 
                + last + " " + new Timestamp(last) + " " + (now - last));
        if (ltime > (now - last)) {
            return false;
        }
        return true;
    }

    private String findActivity(Tflowinstance ins, BPMProcessor c, String pid) {
        List<String> s = c.findActualStates();
        LOGGER.debug("Actividad: FitBank = " + ins.getActividadactual() + "; Bpm = " + s);
        boolean valid = false;
        String activity = "";
        for (String val : s) {
            if (ins.getActividadactual().indexOf(val) > -1) {
                valid = true;
                activity = val;
                break;
            }
        }
        LOGGER.debug("El flujo " + pid + " tiene un estado " + (valid ? "" : "in") + "valido");
        if (!valid) {
            return null;
        }
        return activity;
    }

    private void manageTimeout(BPMProcessor c, String pid) throws Exception {
        Helper.beginTransaction();
        try {

            Thread.sleep(100);
            Tflowinstance ins = this.findInstance(pid);
            if (ins == null) {
                return;
            } else {
                LOGGER.debug("Registro en TINSTANCIAFLUJO encontrado: " + ins);
            }

            String activity = this.findActivity(ins, c, pid);
            if (activity == null) {
                return;
            }

            String sub = this.findRealId(c, activity);
            if (sub == null) {
                return;
            } else {
                LOGGER.debug("ProcessID #" + pid + " para la actividad " 
                        + activity + " encontrado: " + sub);
            }

            c = BPMProcessor.findProcessInstanceById(sub);
            if (c != null) {
                Set<String> varNames = c.getVariableNames();
                LOGGER.debug("Variables encontradas en PID#" + sub + ": " + varNames);
                if (!varNames.contains("timeAuth") || !varNames.contains("detail")) {
                    return;
                }
                String time = (String) c.getVariable("timeAuth");
                Detail detail = (Detail) c.getVariable("detail");
                if (detail == null || !this.validateTimeout(detail, time, pid, sub)) {
                    return;
                }
                this.timeoutHistory(detail, c);

                BPMProcessor c1 = BPMProcessor.findProcessInstanceById(c.getPid());
                c1.sendSign("TIMEOUT");
            }

            Helper.commitTransaction();
        } catch (Exception e) {
            LOGGER.error("Excepcion al procesar el flujo, haciendo rollback", e);
            try {
                Helper.rollbackTransaction();
            } catch (Exception e1) {
                LOGGER.error("No se pudo hacer rollback", e1);
            }
            throw e;
        }
    }

    private Integer obtainParameterNumber(String name, Integer company) {
        //FIXME: Método copiado desde la clase ParameterHelper en negocio-general
        //para quitar dependencia a negocio de BPM. Incidencia #6490.
        String sqlNumero = "select tcps.VALORNUMERICO from "
                + "tcompaniaparametrossistema tcps where tcps.CPARAMETROSISTEMA=:name "
                + "and tcps.CPERSONA_COMPANIA=:company and tcps.fhasta=:fhasta";
        SQLQuery sqlPNumero;
        sqlPNumero = Helper.getSession().createSQLQuery(sqlNumero);
        sqlPNumero.setString("name", name);
        sqlPNumero.setInteger("company", company);
        sqlPNumero.setTimestamp("fhasta", ApplicationDates.DEFAULT_EXPIRY_TIMESTAMP);
        Object num = sqlPNumero.uniqueResult();

        if (num == null) {
            throw new FitbankException("GEN666", "NO SE ENCUENTRA PARAMETRIZADO {0} EN LA BASE DE DATOS",
                    name);
        }

        return (Integer) BeanManager.convertObject(num, Integer.class);
    }

    @Override
    public void shutdown() {
    }

    @Override
    public Integer getPeriod() {
        return BPMProperties.getConfig().getInt("fit.bpm.timeouts.period");
    }

}
