package br.com.centralit.citcorpore.negocio.alcada;
import java.sql.Date;
import java.util.ArrayList;
import java.util.List;

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

import br.com.centralit.citcorpore.bean.AlcadaDTO;
import br.com.centralit.citcorpore.bean.CentroResultadoDTO;
import br.com.centralit.citcorpore.bean.EmpregadoDTO;
import br.com.centralit.citcorpore.bean.ParametroDTO;
import br.com.centralit.citcorpore.bean.SolicitacaoServicoDTO;
import br.com.centralit.citcorpore.bean.UsuarioDTO;
import br.com.centralit.citcorpore.bpm.negocio.ExecucaoSolicitacao;
import br.com.centralit.citcorpore.integracao.EmpregadoDao;
import br.com.centralit.citcorpore.integracao.ParametroDao;
import br.com.centralit.citcorpore.integracao.UsuarioDao;
import br.com.centralit.citcorpore.util.Enumerados.HttpMethodEnum;
import br.com.centralit.citcorpore.util.Enumerados.RestParameterTypeEnum;
import br.com.centralit.citcorpore.util.Enumerados.TipoAlcada;
import br.com.citframework.dto.IDto;
import br.com.citframework.excecao.LogicException;
import br.com.citframework.integracao.CrudDaoDefaultImpl;
import br.com.citframework.integracao.TransactionControler;
import br.com.citframework.util.UtilDatas;
import br.com.citframework.util.UtilStrings;
import br.com.citframework.util.rest.RestExecution;
import br.com.citframework.util.rest.RestOutput;

import com.google.gson.Gson;

public abstract class AlcadaImpl implements IAlcada {
    
    protected TransactionControler tc = null;
    
    protected String urlAlcada = null;
    
    protected Gson gson = new Gson();
    
    // TODO COLOCADO APENAS PARA TER UM CONTROLE PELO LOG DO QUE ESTAVA SENDO EXECUTADO, FAVOR REMOVER APOS A HOMOLOGAÇÃO
 	private static Logger LOGGER_ALCADA = Logger.getLogger(AlcadaImpl.class);

 	private boolean isVazio(String value) {
 		return value == null || value.trim().isEmpty();
 	}
 	
 	public static boolean isNovaAlcada() throws Exception {
    	ParametroDTO parametroDto = new ParametroDao().getValue("ALCADA", "NOVA_ALCADA", new Integer(1));
    	return parametroDto != null && parametroDto.getValor() != null && parametroDto.getValor().equalsIgnoreCase("S");
    }
    
    public String getUrlAlcada() throws Exception {
    	if (urlAlcada == null) {
	    	ParametroDTO parametroDto = new ParametroDao().getValue("ALCADA", "URL_ALCADA", new Integer(1));
	    	if (parametroDto == null || parametroDto.getValor() == null || parametroDto.getValor().trim().equals("")) {
	            throw new LogicException("Parâmetro URL_ALCADA não definido"); 
	    	};
	    	urlAlcada = parametroDto.getValor();
    	}
    	return urlAlcada;
    }

    protected abstract TipoAlcada getTipoAlcada();
    
    protected abstract String getFinalidade(IDto objetoNegociotDto);
    
    protected abstract ExecucaoSolicitacao getExecucaoSolicitacao();
    
    protected abstract double getValorParaAprovacao(IDto objetoNegociotDto, TransactionControler tc) throws Exception;
    
    protected RestOutput executaRest(String operacao, String url, String parametros) throws Exception{
    	RestOutput output = null;
    	
    	LOGGER_ALCADA.info("Conectando a URL do CITGRP: " + url);
    	LOGGER_ALCADA.info("	Operação a ser executada no CIT-Alcada: " + operacao);
    	LOGGER_ALCADA.info("	Parametros enviados ao CIT-Alcada: " + parametros);
    	
    	try {
            output = RestExecution.execute(HttpMethodEnum.POST, url, RestParameterTypeEnum.JSON, parametros, "UTF-8", 30000);
    	} catch (Exception e) {
			String erro = "Erro na execução da alçada para a operação '"+operacao+"': "+e.getMessage();
            throw new LogicException(erro);
		}
		
		if (output != null && output.getHttpStatus() != 200) {
			String erro = "Erro na execução da alçada para a operação '"+operacao+"' "+"(HTTP Status "+output.getHttpStatus()+")";
			erro += UtilStrings.isNotVazio(output.getError()) ? ": " + output.getError() : "";
            throw new LogicException(erro);
		}
		
		return output;
    }

    protected JSONObject montaSolicitacaoJSON(TipoAlcada tipoAlcada
											, String codigoCentroResultado
											, String usuarioSolicitante
											, Date dataReferencia
											, String requisicaoOrigem
											, String identificador
											, String tipoUtilizacao
											, double valor) throws Exception {
		
    	if (isVazio(codigoCentroResultado)) {
    		throw new LogicException("Erro na execução de alçada - Centro de Resultado não informado");
    	}
    	if (isVazio(requisicaoOrigem)) {
    		throw new LogicException("Erro na execução de alçada - Requisição Origem não informada");
    	}
    	if (isVazio(identificador)) {
    		throw new LogicException("Erro na execução de alçada - Identificador não informado");
    	}
    	if (isVazio(tipoUtilizacao)) {
    		throw new LogicException("Erro na execução de alçada - Tipo de Utilização não informado");
    	}
    	
    	JSONObject alcada = new JSONObject();
    	alcada.put("identificador", tipoAlcada.name());
    	
    	JSONObject centroResultado = new JSONObject();
    	centroResultado.put("codigo", codigoCentroResultado);
    	
    	JSONObject solicitante = new JSONObject();
    	solicitante.put("username", usuarioSolicitante);
    	
    	JSONObject utilizacao = new JSONObject();
    	utilizacao.put("nome", tipoUtilizacao.equals("I") ? "INTERNO" : "ATENDIMENTO_CLIENTE");
    	
    	JSONObject solicitacaoAlcada = new JSONObject();
    	solicitacaoAlcada.put("alcada", alcada);
    	solicitacaoAlcada.put("centroResultado", centroResultado);
    	solicitacaoAlcada.put("solicitante", solicitante);
    	solicitacaoAlcada.put("dataReferencia", UtilDatas.dateToJson(dataReferencia));
    	solicitacaoAlcada.put("requisicaoOrigem", requisicaoOrigem);
    	solicitacaoAlcada.put("identificador", identificador);
    	solicitacaoAlcada.put("tipoUtilizacao", utilizacao);
    	solicitacaoAlcada.put("valor", valor);
    	
    	return solicitacaoAlcada;
	}
    
    protected AlcadaDTO solicitaAlcada(TipoAlcada tipoAlcada
									, String codigoCentroResultado
									, String usuarioSolicitante
									, Date dataReferencia
									, String requisicaoOrigem
									, String tipoUtilizacao
									, double valor) throws Exception {

    	
    	JSONObject solicitacaoAlcada = this.montaSolicitacaoJSON(tipoAlcada, codigoCentroResultado, usuarioSolicitante, dataReferencia, requisicaoOrigem, requisicaoOrigem, tipoUtilizacao, valor);
    	
   		return retornaResponsaveis(solicitacaoAlcada);
    }
    
    protected AlcadaDTO registraAprovacao(TipoAlcada tipoAlcada
									, String codigoCentroResultado
									, String usuarioSolicitante
									, String usuarioAprovador
									, Date dataReferencia
									, String requisicaoOrigem
									, String identificador
									, String tipoUtilizacao
									, double valor) throws Exception {
		
		
		JSONObject solicitacaoAlcada = this.montaSolicitacaoJSON(tipoAlcada, codigoCentroResultado, usuarioSolicitante, dataReferencia, requisicaoOrigem, identificador, tipoUtilizacao, valor);
		
		if (usuarioAprovador != null) {
			solicitacaoAlcada.put("usernameAprovador", usuarioAprovador);
		}
		
		return registraAlcada(solicitacaoAlcada);
	}

    protected AlcadaDTO registraAlcada(JSONObject solicitacaoAlcada) throws Exception {
    	String url = this.getUrlAlcada();
    	
    	RestOutput output = this.executaRest("Registro da alçada", url, solicitacaoAlcada.toString());
		
        solicitacaoAlcada = new JSONObject(output.getResponse());
        
        AlcadaDTO alcadaDto = new AlcadaDTO();
        alcadaDto.setId(solicitacaoAlcada.getLong("id"));
        
        JSONArray responsaveis = solicitacaoAlcada.getJSONArray("responsaveis");

        return montaColecaoResponsaveis(alcadaDto, responsaveis);
	}

    protected void registraAprovacoes(JSONArray solicitacoes) throws Exception {
    	String url = this.getUrlAlcada() + "/registra";
    	
    	this.executaRest("Registro da alçada", url, solicitacoes.toString());
	}

	protected AlcadaDTO retornaResponsaveis(JSONObject solicitacaoAlcada) throws Exception {
    	String url = this.getUrlAlcada() + "/getResponsaveis";
    	
    	RestOutput output = this.executaRest("Pesquisa de responsáveis da alçada", url, solicitacaoAlcada.toString());
		
		JSONObject response = new JSONObject(output.getResponse());
		JSONArray responsaveis = response.getJSONArray("payload");

		AlcadaDTO alcadaDto = new AlcadaDTO();
        return this.montaColecaoResponsaveis(alcadaDto, responsaveis);
	}

	protected AlcadaDTO montaColecaoResponsaveis(AlcadaDTO alcadaDto, JSONArray responsaveis) throws Exception {
		alcadaDto.setJSONResponsaveis(responsaveis);
		
        List<EmpregadoDTO> colResponsaveis = new ArrayList<EmpregadoDTO>();
        List<UsuarioDTO> colUsuariosResponsaveis = new ArrayList<UsuarioDTO>();
        
        UsuarioDao usuarioDao = new UsuarioDao();
        setTransacaoDao(usuarioDao);

        LOGGER_ALCADA.info("Responsaveis retornados: ");
        for (int i = 0; i < responsaveis.length(); i++) {
        	JSONObject responsavel = responsaveis.getJSONObject(i);
        	String login = responsavel.getJSONObject("usuario").getString("username");
        	UsuarioDTO usuarioDto = usuarioDao.restoreByLogin(login);
        	if (usuarioDto != null) {
        		LOGGER_ALCADA.info("	Responsavel: " + login + ".  Habilitado: " + responsavel.getBoolean("habilitado"));
        		
            	if (responsavel.getBoolean("habilitado")) {
            		colUsuariosResponsaveis.add(usuarioDto);
            	}
            	
            	EmpregadoDTO empregadoDto = this.recuperaEmpregado(usuarioDto.getIdEmpregado());
            	if (empregadoDto != null) {
                	if (responsavel.getBoolean("habilitado")) {
                		colResponsaveis.add(empregadoDto);
                	}
            	}
        	}
		}
        alcadaDto.setColResponsaveis(colResponsaveis);
        alcadaDto.setColUsuariosResponsaveis(colUsuariosResponsaveis);
        
        return alcadaDto;
	}
	

	protected EmpregadoDTO recuperaEmpregado(Integer idEmpregado) throws Exception {
        EmpregadoDTO empregadoDto = new EmpregadoDTO();
        empregadoDto.setIdEmpregado(idEmpregado);
        EmpregadoDao empregadoDao = new EmpregadoDao();
        setTransacaoDao(empregadoDao);
        return (EmpregadoDTO) empregadoDao.restore(empregadoDto);
    }
    
    protected EmpregadoDTO recuperaEmpregadoPeloLogin(String login) throws Exception {
        UsuarioDao usuarioDao = new UsuarioDao();
        setTransacaoDao(usuarioDao);
        
        UsuarioDTO usuarioDto = usuarioDao.restoreByLogin(login);
        if (usuarioDto != null){
        	return this.recuperaEmpregado(usuarioDto.getIdEmpregado());
        }else{
        	return null;
        }
    }

    protected UsuarioDTO recuperaUsuarioPeloIdEmpregado(Integer idEmpregado) throws Exception {
        UsuarioDao usuarioDao = new UsuarioDao();
        setTransacaoDao(usuarioDao);
        
        return usuarioDao.restoreAtivoByIdEmpregado(idEmpregado);
    }

    protected void setTransacaoDao(CrudDaoDefaultImpl dao) throws Exception {
        if (tc != null)
            dao.setTransactionControler(tc);
    }
    
	protected void aprova(TipoAlcada tipoAlcada, UsuarioDTO aprovador, Integer identificador, double valorAprovado, TransactionControler tc) throws Exception {
    	if (!isNovaAlcada())
			return;
		
    	this.tc = tc;

    	JSONObject alcada = new JSONObject();
    	alcada.put("identificador", tipoAlcada.name());
    	
    	JSONObject solicitacaoAlcada = new JSONObject();
    	solicitacaoAlcada.put("alcada", alcada);
    	solicitacaoAlcada.put("usernameAprovador", aprovador.getLogin());
    	solicitacaoAlcada.put("identificador", ""+identificador);
    	solicitacaoAlcada.put("valor", valorAprovado);
		
    	String url = this.getUrlAlcada() + "/aprovaPeloIdentificador";
    	
    	this.executaRest("Aprovação da alçada", url, solicitacaoAlcada.toString());
	}

	protected void cancelaAprovacao(TipoAlcada tipoAlcada, Integer identificador, TransactionControler tc) throws Exception {
    	if (!isNovaAlcada())
			return;
		
    	this.tc = tc;

    	JSONObject alcada = new JSONObject();
    	alcada.put("identificador", tipoAlcada.name());
    	
    	JSONObject solicitacaoAlcada = new JSONObject();
    	solicitacaoAlcada.put("alcada", alcada);
    	solicitacaoAlcada.put("identificador", ""+identificador);

    	String url = this.getUrlAlcada() + "/cancelaAprovacaoPeloIdentificador";
    	
    	this.executaRest("Cancelamento de aprovação da alçada", url, solicitacaoAlcada.toString()); 
    }

	protected AlcadaDTO recuperaSolicitacao(TipoAlcada tipoAlcada, Integer identificador, TransactionControler tc) throws Exception {
    	if (!isNovaAlcada())
			return null;
		
    	this.tc = tc;

    	JSONObject alcada = new JSONObject();
    	alcada.put("identificador", tipoAlcada.name());
    	
    	JSONObject solicitacaoAlcada = new JSONObject();
    	solicitacaoAlcada.put("alcada", alcada);
    	solicitacaoAlcada.put("identificador", ""+identificador);

    	String url = this.getUrlAlcada() + "/recuperaPeloIdentificador";
    	
    	RestOutput output = this.executaRest("Recuperação da solicitação de alçada", url, solicitacaoAlcada.toString());
		
    	if (output.getResponse() == null || UtilStrings.stringVazia(output.getResponse()) || output.getResponse().equalsIgnoreCase("null")) {
    		return null;
    	}else{
    		solicitacaoAlcada = new JSONObject(output.getResponse());
            JSONArray responsaveis = solicitacaoAlcada.getJSONArray("responsaveis");
            JSONObject situacao = solicitacaoAlcada.getJSONObject("situacao");

            AlcadaDTO alcadaDto = gson.fromJson(output.getResponse(), AlcadaDTO.class);
            alcadaDto = montaColecaoResponsaveis(alcadaDto, responsaveis);
            alcadaDto.setAprovada(situacao != null && situacao.getString("nome").equalsIgnoreCase("APROVADA"));
            
            return alcadaDto;
    	}
    }

	@Override
    public AlcadaDTO determinaAlcada(IDto objetoNegocioDto, CentroResultadoDTO centroCustoDto, TransactionControler tc) throws Exception {
    	this.tc = tc;

    	if (isNovaAlcada()) {
        	SolicitacaoServicoDTO solicitacaoServicoDto = (SolicitacaoServicoDTO) objetoNegocioDto;
        	
        	UsuarioDTO solicitante = this.recuperaUsuarioPeloIdEmpregado(solicitacaoServicoDto.getIdSolicitante());
        	
        	double valor = this.getValorParaAprovacao(solicitacaoServicoDto, tc);

        	return this.solicitaAlcada( this.getTipoAlcada()
									   , centroCustoDto.getCodigoCentroResultado()
									   , solicitante != null ? solicitante.getLogin() : null
									   , UtilDatas.getDataAtual()
									   , ""+solicitacaoServicoDto.getIdSolicitacaoServico()
									   , this.getFinalidade(solicitacaoServicoDto)
									   , valor);
        }else{
        	AlcadaDTO alcadaDto = new AlcadaDTO(this.getTipoAlcada().name());
            alcadaDto.setColResponsaveis(AlcadaProcessoNegocio.getInstance().getResponsaveis((SolicitacaoServicoDTO) objetoNegocioDto, centroCustoDto, tc));
            
            return alcadaDto;
        }
    }
    
	@Override
    public AlcadaDTO registraAprovacao(IDto objetoNegocioDto, CentroResultadoDTO centroCustoDto, UsuarioDTO aprovador, Integer identificador, double valorAprovado, TransactionControler tc) throws Exception {
    	if (!isNovaAlcada())
			return null;
		
    	this.tc = tc;

    	SolicitacaoServicoDTO solicitacaoServicoDto = (SolicitacaoServicoDTO) objetoNegocioDto;
    	
    	UsuarioDTO solicitante = this.recuperaUsuarioPeloIdEmpregado(solicitacaoServicoDto.getIdSolicitante());
    	
    	return this.registraAprovacao(this.getTipoAlcada()
								   , centroCustoDto.getCodigoCentroResultado()
								   , solicitante != null ? solicitante.getLogin() : null
								   , aprovador != null ? aprovador.getLogin() : null
								   , UtilDatas.getDataAtual()
								   , ""+solicitacaoServicoDto.getIdSolicitacaoServico()
								   , ""+identificador
								   , this.getFinalidade(solicitacaoServicoDto)
								   , valorAprovado);
    }
    
	@Override
	public void registraAprovacoes(IDto objetoNegocioDto, CentroResultadoDTO centroCustoDto, List<Integer> identificadores, double valor, TransactionControler transacao) throws Exception {
    	if (!isNovaAlcada())
			return;
		
    	this.tc = transacao;

    	SolicitacaoServicoDTO solicitacaoServicoDto = (SolicitacaoServicoDTO) objetoNegocioDto;
    	
    	UsuarioDTO solicitante = this.recuperaUsuarioPeloIdEmpregado(solicitacaoServicoDto.getIdSolicitante());
    	
    	int i = 0;
    	JSONArray solicitacoes = new JSONArray();
    	for (Integer identificador: identificadores) {
        	JSONObject solicitacaoAlcada = this.montaSolicitacaoJSON(this.getTipoAlcada()
        														   , centroCustoDto.getCodigoCentroResultado()
        														   , solicitante != null ? solicitante.getLogin() : null
        														   , UtilDatas.getDataAtual()
        														   , ""+solicitacaoServicoDto.getIdSolicitacaoServico()
        														   , ""+identificador
        														   , this.getFinalidade(solicitacaoServicoDto)
        														   , valor);
    		solicitacoes.put(i, solicitacaoAlcada);
    		i++;
    	}

    	this.registraAprovacoes(solicitacoes);
	}

	@Override
	public void aprova(UsuarioDTO aprovador, Integer identificador, double valorAprovado, TransactionControler tc) throws Exception {
		this.aprova(this.getTipoAlcada(), aprovador, identificador, valorAprovado, tc);
	}

	@Override
	public void cancelaAprovacao(Integer identificador, TransactionControler tc) throws Exception {
		if (this.recupera(identificador, tc) != null) {
			this.cancelaAprovacao(this.getTipoAlcada(), identificador, tc);
		}
	}

	@Override
	public AlcadaDTO recupera(Integer identificador, TransactionControler tc) throws Exception {
		return this.recuperaSolicitacao(this.getTipoAlcada(), identificador, tc);
	}

}
