package com.fp.persistence.commondb.log;

import java.lang.reflect.Field;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;

import com.fp.common.helper.BeanManager;
import com.fp.common.logger.APPLogger;
import com.fp.dto.data.SaveData;
import com.fp.dto.hb.HibernateBean;
import com.fp.dto.save.SaveRequest;
import com.fp.persistence.commondb.PersistenceHelper;
import com.fp.persistence.commondb.helper.FormatDates;
import com.fp.persistence.pgeneral.date.TgeneAccountingDateBranch;
import com.fp.persistence.pgeneral.date.TgeneAccountingDateBranchKey;
import com.fp.persistence.pgeneral.safe.TsafeAudit;
import com.fp.persistence.pgeneral.safe.TsafeAuditInsDel;
import com.fp.persistence.pgeneral.safe.TsafeAuditInsDelKey;
import com.fp.persistence.pgeneral.safe.TsafeAuditKey;
import com.fp.persistence.pgeneral.safe.TsafeUser;
import java.util.Calendar;

/**
 * Clase que se encarga del menejo de auditoria de de cambios en tablas.
 *
 * @author Jorge Vaca.
 * @version 2.1
 */
public class Audit {

    /**
     * Metodo que obtiene lista de campos del registro, y procesa por campo , si el valor cambio se genera un registro
     * de auditoria.
     *
     * @param newBean Datos del registro nuevo.
     * @param oldBean Datos del registro anterior.
     * @throws Exception
     */
    public static void process(HibernateBean newBean, HibernateBean oldBean) throws Exception {
        Field f[] = newBean.getClass().getDeclaredFields();
        Timestamp t = null;
        for (Field obj : f) {
            if (obj.getName().compareTo("pk") == 0 || obj.getName().compareTo("hashValue") == 0
                    || obj.getName().compareTo("class") == 0 || obj.getName().compareTo("serialVersionUID") == 0) {
                continue;
            }
            if (t == null) {
                t = FormatDates.getInstance().getDataBaseTimestamp();
            }
            Audit.processByField(obj, newBean, oldBean, t);
        }
    }

    /**
     * Metodo que verifica si los datos del campo son diferente de los valores del registro anterior.
     *
     * @param f Nombre del campo.
     * @param newBean Datos del nuevo registro.
     * @param oldBean Datos del registro anterior.
     * @throws Exception
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private static void processByField(Field f, HibernateBean newBean, HibernateBean oldBean, Timestamp t) throws Exception {
        Object newvalue;
        try {
            newvalue = BeanManager.getBeanAttributeValue(newBean, f.getName());
        } catch (NoSuchMethodException e) {
            return;
        }
        Object oldvalue;
        try {
            oldvalue = BeanManager.getBeanAttributeValue(oldBean, f.getName());
        } catch (Exception e) {
            return;
        }
        if (newvalue == null && oldvalue == null) {
            return;
        }
        if ((newvalue == null && oldvalue != null) || (newvalue != null && oldvalue == null)) {
            saveLog(newBean.getClass().getSimpleName(), f, newvalue, oldvalue, t);
            return;
        }
        if (newvalue instanceof Comparable) {
            if (((Comparable) newvalue).compareTo(oldvalue) != 0) {
                saveLog(newBean.getClass().getSimpleName(), f, newvalue, oldvalue, t);
            }
        }
    }

    /**
     * Crea e inserta un rgistro en la tabla de auditoria TsafeAudit.
     *
     * @param beanName Nombre del bean.
     * @param f Nombre del campo de la base.
     * @param newvalue Nuevo valor.
     * @param oldvalue Valor anterior.
     * @throws Exception
     */
    private static void saveLog(String beanName, Field f, Object newvalue, Object oldvalue, Timestamp t) throws Exception {
        SaveRequest sr = SaveData.getSaveRequest();
        //Crea un objeto con los datos a almacenar auditoria.
        TsafeAuditKey key = new TsafeAuditKey();
        //TgeneAccountingDateBranchKey dkey = new TgeneAccountingDateBranchKey(0, sr.getCompany());
        //TgeneAccountingDateBranch d = TgeneAccountingDateBranch.find(PersistenceHelper.getEntityManager(), dkey);
        Timestamp fecha = new Timestamp(Calendar.getInstance().getTimeInMillis());
        key.setChangedate(fecha);//d.getAccountingdate()
        key.setTablename(beanName.toUpperCase());
        key.setColumnname(f.getName().toUpperCase());
        key.setPartitiondb(FormatDates.getPartition(key.getChangedate()));
        key.setRealdate(t);
        TsafeAudit obj = new TsafeAudit();
        obj.setPk(key);
        obj.setCompanycode(sr.getCompany());
        obj.setOfficecode(sr.getOfficeCode());
        obj.setBranchcode(sr.getBranchCode());
        obj.setTerminalcode(sr.getTerminalCode());
        obj.setUsercode(sr.getUser());
        obj.setTransactionmodule(sr.getTransactionModule());
        obj.setTransactioncode(sr.getTransactionCode());
        obj.setTransactionversion(sr.getTransactionVersion());
        obj.setBrowser(sr.getBrowser());
        obj.setOperativesystem(sr.getOperativesystem());
        if (oldvalue != null) {
            obj.setOldvalue(oldvalue.toString());
        }
        if (newvalue != null) {
            obj.setNewvalue(newvalue.toString());
        }
        if(sr.getUser()!=null){
        	TsafeUser user= TsafeUser.find(PersistenceHelper.getEntityManager(), sr.getUser());
        	if(user!=null){
        		obj.setPersoncode(user.getPersoncode());
        	}
        }
        setAdicionalData(obj);
        //Almacena el registro en la base.
        PersistenceHelper.save(obj);
    }

    /**
     * Metodo que registra en el log de auditoria los registros nuevos de una tabla.
     *
     * @param pBean Datos del registro nuevo.
     * @throws Exception
     */
    public static void insert(HibernateBean pBean) throws Exception {
        insertdelete(pBean, true);
    }

    /**
     * Metodo que registra en el log de auditoria los registros nuevos de una tabla.
     *
     * @param pBean Datos del registro nuevo.
     * @throws Exception
     */
    public static void delete(HibernateBean pBean) throws Exception {
        insertdelete(pBean, false);
    }

    /**
     * Metodo que registra en el log de auditoria los registros nuevos de una tabla.
     *
     * @param pBean Datos del registro nuevo.
     * @throws Exception
     */
    private static void insertdelete(HibernateBean pBean, boolean insert) throws Exception {
        Field f[] = pBean.getClass().getDeclaredFields();
        Map<String, Object> m = new HashMap<String, Object>();
        for (Field obj : f) {
            if (obj.getName().compareTo("hashValue") == 0
                    || obj.getName().compareTo("class") == 0 || obj.getName().compareTo("serialVersionUID") == 0) {
                continue;
            }
            String value = getBeanValue(pBean, obj.getName());
            m.put(obj.getName(), value);
        }
        if (!m.isEmpty()) {
            saveLogInsertDelete(pBean.getClass().getSimpleName(), m, insert);
        }
    }

    /**
     * Entrega el valor de un campo.
     *
     * @param pBean bean a obtener el valor de un campo.
     * @param pField Nombre del campo a obtener el valor.
     * @return String.
     * @throws Exception
     */
    private static String getBeanValue(HibernateBean pBean, String pField) throws Exception {
        String newvalue;
        try {
            newvalue = BeanManager.getBeanAttributeValue(pBean, pField).toString();
            if (newvalue.contains("from")) {
                //Para que no envie sentecias JPL que estan en el bean.
                return null;
            }
        } catch (Exception e) {
            return null;
        }
        return newvalue;
    }

    /**
     * Crea un registro en la base con datos de insert o update de un registro.
     *
     * @param beanName Nombre de la tabla.
     * @param m Map con los campos de la tabla.
     * @param insert true indica que inserta un registro, false indica que se elimian un registro de la base.
     * @throws Exception
     */
    private static void saveLogInsertDelete(String beanName, Map<String, Object> m, boolean insert) throws Exception {
        Timestamp t = FormatDates.getInstance().getDataBaseTimestamp();
        Timestamp change = new Timestamp(Calendar.getInstance().getTimeInMillis());
        SaveRequest sr = SaveData.getSaveRequest();
        if (sr == null) {
            return;
        }
        //Crea un objeto con los datos a almacenar auditoria.
        TsafeAuditInsDelKey key = new TsafeAuditInsDelKey();
        //TgeneAccountingDateBranchKey dkey = new TgeneAccountingDateBranchKey(0, sr.getCompany());
        //TgeneAccountingDateBranch d = TgeneAccountingDateBranch.find(PersistenceHelper.getEntityManager(), dkey);
        //Date fecha = new Date(t.getTime());
        Timestamp real = new Timestamp(Calendar.getInstance().getTimeInMillis());

        key.setChangedate(change);
        key.setTablename(beanName.toUpperCase());
        key.setPartitiondb(FormatDates.getPartition(key.getChangedate()));
        key.setRealdate(real);
        TsafeAuditInsDel obj = new TsafeAuditInsDel();
        obj.setPk(key);
        obj.setCompanycode(sr.getCompany());
        obj.setOfficecode(sr.getOfficeCode());
        obj.setBranchcode(sr.getBranchCode());
        obj.setTerminalcode(sr.getTerminalCode());
        obj.setUsercode(sr.getUser());
        obj.setTransactionmodule(sr.getTransactionModule());
        obj.setTransactioncode(sr.getTransactionCode());
        obj.setTransactionversion(sr.getTransactionVersion());
        obj.setBrowser(sr.getBrowser());
        obj.setOperativesystem(sr.getOperativesystem());
        if (insert) {
            obj.setIsinsert("Y");
            obj.setIsdelete("N");
        } else {
            obj.setIsinsert("N");
            obj.setIsdelete("Y");
        }
        obj.setRecvalue(m.toString());
        setAdicionalData(obj);
        
        if(sr.getUser()!=null){
        	TsafeUser user= TsafeUser.find(PersistenceHelper.getEntityManager(), sr.getUser());
        	if(user!=null){
        		obj.setPersoncode(user.getPersoncode());
        	}
        }
        
        //Almacena el registro en la base.
        PersistenceHelper.save(obj);
    }
/**
 * 
 * @param obj
 * @throws Exception 
 */
    private static void setAdicionalData(HibernateBean obj) throws Exception {
    	try{
	        try {
	            BeanManager.setBeanAttributeValue(obj, "account", SaveData.getAccount().substring(0, 19));
	        } catch (Exception e) {
	            //NO hacer nada,
	            APPLogger.getLogger().debug(e.getStackTrace()[0]);
	        }
	        try {
	            BeanManager.setBeanAttributeValue(obj, "personcode", SaveData.getPersoncode() == null ? null : Integer.valueOf(SaveData.getPersoncode()));
	        } catch (Exception e) {
	            //NO hacer nada,
	            APPLogger.getLogger().debug(e.getStackTrace()[0]);
	        }
	        try {
	            BeanManager.setBeanAttributeValue(obj, "solicitudenumber", SaveData.getSolicitudenumber().substring(0, 19));
	        } catch (Exception e) {
	            //NO hacer nada,
	            APPLogger.getLogger().debug(e.getStackTrace()[0]);
	        }
    	} catch(Throwable e) {
    		APPLogger.getLogger().error(e.getMessage(), e);
    	}
    }
}