package com.fp.bpm.actions;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;

import com.fp.bpm.save.Save;
import com.fp.common.exception.APPException;
import com.fp.common.helper.BeanManager;
import com.fp.common.helper.Constant;
import com.fp.dto.AbstractDataTransport;
import com.fp.dto.Request;
import com.fp.dto.Response;
import com.fp.dto.hb.HibernateBean;
import com.fp.dto.hb.History;
import com.fp.dto.save.DtoSave;
import com.fp.dto.save.SaveBean;
import com.fp.dto.save.SaveRequest;
import com.fp.persistence.commondb.PersistenceHelper;
import com.fp.persistence.commondb.PersistenceManager;

/**
 * Clase utilitaria que crea un SaveRequest dado un request de mantenimiento.
 * 
 * @author Jorge Vaca.
 * @version 2.1
 */
public class SaveActionHelper {

    public void addSaveBean(Request request, SaveRequest saverequest) throws Exception {
        Map<String, DtoSave> m = request.getSaveTables();
        if (m == null) {
            return;
        }
        Set<String> s = m.keySet();
        for (String key : s) {
            DtoSave sdto = m.get(key);
            this.addCamposControl(sdto, saverequest);
            List<Object> lreg = creaObjetosPorTabla(sdto.getLupdated(), key, sdto.getBeanname());

            SaveBean sb = new SaveBean(sdto.getBeanname());
            sb.setDeletedRecords(sdto.getLdeleted());
            Save save = new Save();
            save.deleteBean(sb);
            sb.setDeletedRecords(new ArrayList<Object>());

            sb.addModified(lreg, sdto.isIsForm());
            sb.addInsertObject(sdto.getLinsert(), sdto.isIsForm());
            Integer pos = sdto.getPosition();
            if (pos != null) {
                saverequest.putSaveBean(key, sb, pos);
            } else {
                saverequest.putSaveBean(key, sb);
            }
        }
    }

    /**
     * Metodo que entrega una lista de Objetos con sus datos.
     * 
     * @param mdatos Map con los datos a crear un objeto y fijar datos.
     * @param criterio Criterio para armar la lista de objetos, el clriterio es; ins (Insert), upd (Update), del
     *            (delete)
     * @param isupdate true indica que se realiza un update a la tabla.
     * @param tabla Alias de la tabla a manipular.
     * @return List<Object>
     * @throws Exception
     */
    private static List<Object> creaObjetosPorTabla(List<HashMap<String, Object>> ldatos, String tabla, String nombrebean) throws Exception {
        List<Object> lbeans = new ArrayList<Object>();
        if (ldatos == null || ldatos.isEmpty()) {
            return lbeans;
        }
        for (Map<String, Object> map : ldatos) {
            // En mantenimiento se ecesita el bean para obtener el pk y leer el registro de la base.
            Object bean = SaveActionHelper.getBeanInstance(nombrebean);
            // En el mantenimiento se cambia el bean por el de la base y se completa datos del rq.
            bean = SaveActionHelper.mapToObjeto(bean, map, tabla);
            ((AbstractDataTransport) bean).isupdated = true;
            lbeans.add(bean);
        }
        return lbeans;
    }

    /**
     * 
     * @param tabladto
     * @throws Exception
     */
    private void addCamposControl(DtoSave dtosave, SaveRequest saverequest) throws Exception {
        Map<String, Object> m = dtosave.getMdata();
        Set<String> s = m.keySet();
        for (String key : s) {
            Object obj = m.get(key);
            try {
                BeanManager.setBeanAttributeValue(this, key, obj);
            } catch (NoSuchMethodException e) {
                saverequest.put(key, obj);
            } catch (IllegalArgumentException e1) {
                fijaValorAlObjeto(key, obj, saverequest);
            }
        }
    }

    /**
     * Fija el valor al request convirtiendo el tipo de dato al definido en el request.
     * 
     * @param key Nombre del atrinuto.
     * @param obj Valor a fija al request.
     * @throws Exception
     */
    private void fijaValorAlObjeto(String key, Object obj, SaveRequest saverequest) throws Exception {
        try {
            Class<?> type = null;
            try {
                type = BeanManager.getBeanGetterMethod(this, key).getReturnType();
            } catch (Exception e2) {
                // No hacer nada.
            }
            try {
                obj = BeanManager.convertObject(obj, type);
            } catch (Exception e) {
                // No hacer nada.
            }
            BeanManager.setBeanAttributeValue(this, key, obj);
        } catch (Exception e) {
            saverequest.put(key, obj);
        }
    }

    /**
     * Metodo que crea y entrega una instancia de un bean persistente.
     * 
     * @param beanname Paquete clase del bean a crear.
     * @return Object
     * @throws Exception
     */
    private static Object getBeanInstance(String beanname) throws Exception {

        Object b = (Class.forName(beanname)).newInstance();
        if (!(b instanceof HibernateBean)) {
            return b;
        }
        try {
            Object bkey = Class.forName(beanname + "Key").newInstance();
            BeanManager.setBeanAttributeValue(b, "pk", bkey);
        } catch (ClassNotFoundException e) {
            // si el objeto no tiene pk compuesto no hacer nada.
            return b;
        }
        return b;
    }

    private static Object mapToObjeto(Object bean, Map<String, Object> map, String tabla) throws Exception {
        for (String key : map.keySet()) {
            Object valor = map.get(key);
            setValue(bean, key, valor);
        }
        // Eso se hace solo si es un update.
        if (bean instanceof AbstractDataTransport) {
            // si es update y es de tipo trnsaporte de flip
            bean = SaveActionHelper.merge(bean, map, tabla);
        }
        return bean;
    }

    private static void setValue(Object bean, String campo, Object valor) throws Exception {
        try {
            BeanManager.setBeanAttributeValue(bean, campo, valor);
        } catch (IllegalArgumentException e) {
            Class<?> type = null;
            try {
                type = BeanManager.getBeanGetterMethod(bean, campo).getReturnType();
            } catch (Exception e2) {
                // No hacer nada.
            }
            try {
                valor = BeanManager.convertObject(valor, type);
            } catch (Exception e1) {
                // No hacer nada.
            }
            BeanManager.setBeanAttributeValue(bean, campo, valor);
        } catch (NullPointerException e) {
            throw new APPException("COMMON-0019", "CAMPO: {0} NO EXISTE EN EL BEAN: {1}", campo, bean.getClass().getSimpleName());
        }
    }

    private static Object merge(Object bean, Map<String, Object> map, String tablealias) throws Exception {
        EntityManager em = PersistenceHelper.getEntityManager();
        Object id = BeanManager.getBeanAttributeValue(bean, "pk");
        if ((id instanceof History)) {
            History pk = (History) id;
            if (pk.getDateto() == null) {
                throw new APPException("COMMON-0020", "CAMPO DATETO DEL REGISTRO NO ENVIADO TABLA: {0}", bean.getClass().getSimpleName());
            }
        }
        HibernateBean beanbase = (HibernateBean) em.find(bean.getClass(), id);
        if (beanbase == null) {
            // Si no encuentra un registro a modificar en la base, esta cambiando el pk, o el registro tiene que llegar
            // como un insert
            throw new APPException("COMMON-0021", "TABLA NO PERMITE ACTUALIZAR EL PK: {0}", tablealias);
        }
        HibernateBean boriginal = (HibernateBean) beanbase.cloneMe();
        for (String key : map.keySet()) {
            if (key.compareTo("__id") == 0 || key.compareTo("pk") == 0) {
                continue;
            }
            key = key.replace("pk_", "pk.");
            if ((key.length() >= 3) && (key.substring(0, 3).compareTo("pk.") == 0)) {
                continue;
            }
            if (key.equals("recordversion")) {
                validateOptlocking(map.get(key), beanbase);
            }
            Object value = map.get(key);

            BeanManager.setAbstractDataTransportAttributeValue(beanbase, key, value);
        }
        // va aqui porque en el for anterior se fija el valor de mdatos que llega desde la pantalla.
        ((AbstractDataTransport) beanbase).put("ORIGINALBEAN", boriginal);
        return beanbase;
    }

    /**
     * Valida que el campo optlock que llega en el request coincida con el que esta en la base de datos.s
     * 
     * @param opplockrequest Valor del request del campo optlock.
     * @param beanbase Objeto que contiene los datos de la base de datos de registro que se modifica.
     * @throws Exception
     */
    private static void validateOptlocking(Object opplockrequest, HibernateBean beanbase) throws Exception {
        Object optlockorig = BeanManager.getBeanAttributeValue(beanbase, "recordversion");
        if (optlockorig != null && !opplockrequest.equals(optlockorig)) {
            // Si la version del registro original es diferente el usuario tiene que reconsultar los datos.
            throw new APPException("COMMON-0022", "DATOS DE LA TABLA: {0} MODIFICADOS EN OTRA SESSION RECONSULTE LOS DATOS", beanbase.getClass()
                    .getSimpleName());
        }
    }

    public void addPktoResponse(Request request, Response response) throws Exception {
        Map<String, DtoSave> m = request.getSaveTables();
        if (m == null) {
            return;
        }
        Set<String> s = m.keySet();
        for (String key : s) {
            DtoSave sdto = m.get(key);
            if (!sdto.isReturnpk()) {
                continue;
            }
            if (sdto.getLinsert() != null && !sdto.getLinsert().isEmpty()) {
                AbstractDataTransport t = sdto.getLinsert().get(0);
                if (t instanceof HibernateBean) {
                    List<String> lpkfields = PersistenceManager.getDataBasePKFields(t.getClass());
                    this.addPkbyTable(key, sdto.getLinsert(), lpkfields, response);
                }

            }
        }
    }

    private void addPkbyTable(String key, List<AbstractDataTransport> ldata, List<String> lpkfields, Response response) throws Exception {
        List<Map<String, Object>> lpk = new ArrayList<Map<String, Object>>();
        for (AbstractDataTransport obj : ldata) {
            Map<String, Object> m = this.buildMapPk(obj, lpkfields);
            lpk.add(m);
        }
        response.put(key, lpk);
    }

    private Map<String, Object> buildMapPk(AbstractDataTransport bean, List<String> lpkfields) throws Exception {
        Map<String, Object> m = new HashMap<String, Object>();
        for (String field : lpkfields) {
            String aux = field.toLowerCase();
            Object value = BeanManager.getBeanAttributeValue(bean, aux);

            if (aux.compareTo("pk.dateto") == 0 && value == null) {
                value = Constant.getDefaultExpiryDate();
            }
            m.put(field.replace(".", "_"), value);
        }
        m.put("rowkey", bean.getRowkey());
        return m;
    }
}
