package com.bytesw.bytebot.service;

import com.bytesw.bytebot.model.BusinessParameter;
import com.bytesw.bytebot.model.BusinessParameterConfiguration;
import com.bytesw.bytebot.bean.BusinessParameterBean;
import com.bytesw.bytebot.bean.BusinessParameterConfigurationBean;
import com.bytesw.bytebot.bean.BusinessParameterValueBean;
import com.bytesw.xdf.exception.AlreadyExistsException;
import com.bytesw.xdf.exception.NotFoundException;
import com.bytesw.xdf.service.XDFService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.bytesw.bytebot.repository.BusinessParameterConfigurationRepository;
import com.bytesw.bytebot.repository.BusinessParameterRepository;
import java.math.BigInteger;
import java.time.OffsetDateTime;
import java.util.*;


@Service
@Transactional
public class BusinessParameterService extends XDFService<BusinessParameter, BusinessParameterBean, BigInteger> {
    @Autowired
    BusinessParameterRepository businessParameterRepository;
    @Autowired
    BusinessParameterConfigurationRepository businessParameterConfigurationRepository;
    @Autowired
    private BusinessParameterService businessParameterService;
    @Autowired
    private BusinessParameterConfigurationService businessParameterConfigurationService;

    protected BusinessParameterService(BusinessParameterRepository repository) {
        super(repository);
    }

    @Override
    protected BusinessParameter toModel(BusinessParameter model, BusinessParameterBean bean) {
        if (model == null) {
            model = new BusinessParameter();
        }
        BeanUtils.copyProperties(bean, model);
        model.setRequired("Y".equals(bean.getRequired()));
        return model;
    }

    @Override
    protected BusinessParameterBean toBean(BusinessParameter model) {
        BusinessParameterBean bean = new BusinessParameterBean();
        BeanUtils.copyProperties(model, bean);
        bean.setRequired(model.getRequired() ? "Y" : "N");
        BusinessParameterConfigurationBean businessParameterConfigurationBean = getBusinessConfiguration(model.getId());
        bean.setCurrentValue(businessParameterConfigurationBean.getValue());
        bean.setDateFrom(businessParameterConfigurationBean.getFrom());
        bean.setDateApplyFrom(businessParameterConfigurationBean.getTo());
        bean.setProgrammedValue(businessParameterConfigurationBean.getProgrammedValue());
        bean.setCurrentConfigurationId(businessParameterConfigurationBean.getCurrentConfigurationId());
        bean.setProgrammedConfigurationId(businessParameterConfigurationBean.getProgrammedConfigurationId());
        return bean;
    }

    /**
     * Permite crear un parámetro general utilizando el valor y la fecha de aplicación. Si la fecha no viene entonces se utiliza la fecha actual.
     * <p></p>
     *
     * @param bean
     * @return
     */
    public BusinessParameterValueBean createWithValue(BusinessParameterValueBean bean) {
        Optional<BusinessParameter> businessParameterOptional = businessParameterRepository.findByKey(bean.getKey());
        if (businessParameterOptional.isPresent()) {
            String message = String.format("BusinessParameter is already created: %s. ", bean.getKey());
            throw new AlreadyExistsException(message);
        }
        BusinessParameter businessParameter = new BusinessParameter();
        businessParameter.setKey(bean.getKey());
        businessParameter.setDescription(bean.getDescription());
        businessParameter.setRequired(bean.isRequired());
        businessParameter.setDefaultValue(bean.getDefaultValue());
        businessParameter = businessParameterRepository.save(businessParameter);

        if (bean.getFrom() == null) {
            bean.setFrom(OffsetDateTime.now());
        }

        BusinessParameterConfiguration businessParameterConfiguration = prepareBusinessParameterConfiguration(businessParameter, bean.getValue(), bean.getFrom());
        businessParameterConfiguration.setBusinessParameter(businessParameter);
        businessParameterConfigurationRepository.save(businessParameterConfiguration);

        bean.setId(businessParameter.getId());
        return bean;
    }

    /**
     * Este método parte de que solo se puede un valor actual y/o un valor programado.
     *
     * @param key
     * @param value
     * @return
     */
    public void addValue(String key, String value, OffsetDateTime from) {
        Optional<BusinessParameter> businessParameterOptional = businessParameterRepository.findByKey(key);
        if (!businessParameterOptional.isPresent()) {
            String message = String.format("BusinessParameter %s not found.", key);
            throw new NotFoundException(message);
        }

        if (from == null) {
            from = OffsetDateTime.now();
        } else if (from.compareTo(OffsetDateTime.now()) < 0) {
            String message = String.format("New date (%s) for the parameter (%s)  cannot be less than the current", key, from.toString());
            throw new NotFoundException(message);
        }

        List<BusinessParameterConfiguration> businessParameterConfigurationList = businessParameterConfigurationRepository.findValidByKey(key, from);
        if (businessParameterConfigurationList.isEmpty()) {
            BusinessParameterConfiguration businessParameterConfiguration = prepareBusinessParameterConfiguration(businessParameterOptional.get(), value, from);
            businessParameterConfigurationRepository.save(businessParameterConfiguration);
        } else {
            Collections.sort(businessParameterConfigurationList);
            OffsetDateTime temporallyTo = null;
            for (BusinessParameterConfiguration businessParameterConfiguration : businessParameterConfigurationList) {
                if (businessParameterConfiguration.getTo() != null) {
                    if (temporallyTo != null) {
                        businessParameterConfiguration.setFrom(temporallyTo);
                    }
                    temporallyTo = businessParameterConfiguration.getTo();
                    businessParameterConfiguration.setTo(from);
                    businessParameterConfigurationRepository.save(businessParameterConfiguration);
                    continue;
                }
                if (temporallyTo != null) {
                    businessParameterConfiguration.setFrom(from);
                    businessParameterConfiguration.setValue(value);
                    businessParameterConfigurationRepository.save(businessParameterConfiguration);
                } else {
                    businessParameterConfiguration.setTo(from);
                    businessParameterConfigurationRepository.save(businessParameterConfiguration);

                    businessParameterConfiguration = prepareBusinessParameterConfiguration(businessParameterOptional.get(), value, from);
                    businessParameterConfigurationRepository.save(businessParameterConfiguration);
                }

            }
        }
    }

    private BusinessParameterConfiguration prepareBusinessParameterConfiguration(BusinessParameter businessParameter, String value, OffsetDateTime from) {
        BusinessParameterConfiguration businessParameterConfiguration = new BusinessParameterConfiguration();
        businessParameterConfiguration.setValue(value);
        if (from == null) {
            businessParameterConfiguration.setFrom(OffsetDateTime.now());
        } else {
            businessParameterConfiguration.setFrom(from);
        }
        businessParameterConfiguration.setBusinessParameter(businessParameter);
        return businessParameterConfiguration;
    }

    public String getCurrentValueParameterByKey(String key) {
        String value = businessParameterService.getCurrentValueParameterByKey(key);
//        String value = businessParameterService.getCurrentValue(key);
        if (Objects.isNull(value)) {
            throw new RuntimeException(String.format("Parameter %s not found", key));
        }
        return value;
    }

    @Override
    public void delete(BigInteger id) {
        List<BusinessParameterConfiguration> businessParameterConfigurationList = businessParameterConfigurationRepository.findBusinessParameterConfigurationListByBusinessParameter(id);
        for (BusinessParameterConfiguration businessParameterConfiguration : businessParameterConfigurationList) {
            businessParameterConfigurationRepository.delete(businessParameterConfiguration);
        }
        Optional<BusinessParameter> found = this.repository.findById(id);
        if (!found.isPresent()) {
            throw new NotFoundException();
        } else {
            this.repository.delete(found.get());
        }
    }

    @Override
    public BusinessParameterBean update(BusinessParameterBean bean, BigInteger id) {
        Optional<BusinessParameter> found = this.repository.findById(id);
        if (!found.isPresent()) {
            throw new NotFoundException();
        } else {
            BusinessParameter businesParameter = this.toModel(found.get(), bean);
            BeanUtils.copyProperties(businesParameter, found.get());
            businesParameter = (BusinessParameter) this.repository.save(found.get());
            if (bean.getDateFrom() != null) {
                if (bean.getCurrentConfigurationId() != null) {
                    Optional<BusinessParameterConfiguration> businessParameterConfigurationCurrent = businessParameterConfigurationRepository.findById(bean.getCurrentConfigurationId());
                    businessParameterConfigurationCurrent.get().setFrom(bean.getDateFrom());
                    businessParameterConfigurationCurrent.get().setTo(bean.getDateApplyFrom());
                    businessParameterConfigurationCurrent.get().setValue(bean.getCurrentValue());
                    businessParameterConfigurationRepository.save(businessParameterConfigurationCurrent.get());
                } else {
                    BusinessParameterConfiguration businessParameterConfigurationCurrent = new BusinessParameterConfiguration();
                    businessParameterConfigurationCurrent.setFrom(bean.getDateFrom());
                    businessParameterConfigurationCurrent.setTo(bean.getDateApplyFrom());
                    businessParameterConfigurationCurrent.setValue(bean.getCurrentValue());
                    businessParameterConfigurationCurrent.setBusinessParameter(businesParameter);
                    businessParameterConfigurationRepository.save(businessParameterConfigurationCurrent);
                }
            } else if (bean.getCurrentConfigurationId() != null) {
                Optional<BusinessParameterConfiguration> businessParameterConfigurationCurrent = businessParameterConfigurationRepository.findById(bean.getCurrentConfigurationId());
                businessParameterConfigurationRepository.delete(businessParameterConfigurationCurrent.get());
            }
            if (bean.getDateApplyFrom() != null) {
                if (bean.getProgrammedConfigurationId() != null) {
                    Optional<BusinessParameterConfiguration> businessParameterConfigurationProgrammed = businessParameterConfigurationRepository.findById(bean.getProgrammedConfigurationId());
                    businessParameterConfigurationProgrammed.get().setFrom(bean.getDateApplyFrom());
                    businessParameterConfigurationProgrammed.get().setValue(bean.getProgrammedValue());
                    businessParameterConfigurationRepository.save(businessParameterConfigurationProgrammed.get());
                } else {
                    BusinessParameterConfiguration businessParameterConfigurationProgrammed = new BusinessParameterConfiguration();
                    businessParameterConfigurationProgrammed.setFrom(bean.getDateApplyFrom());
                    businessParameterConfigurationProgrammed.setValue(bean.getProgrammedValue());
                    businessParameterConfigurationProgrammed.setBusinessParameter(businesParameter);
                    businessParameterConfigurationRepository.save(businessParameterConfigurationProgrammed);
                }
            } else if (bean.getProgrammedConfigurationId() != null) {
                Optional<BusinessParameterConfiguration> businessParameterConfigurationProgrammed = businessParameterConfigurationRepository.findById(bean.getProgrammedConfigurationId());
                businessParameterConfigurationRepository.delete(businessParameterConfigurationProgrammed.get());
            }
            return this.toBean(businesParameter);
        }
    }

    //@Override
    public BusinessParameterBean createBP(BusinessParameterBean bean) {
        BusinessParameter model = new BusinessParameter();
        BusinessParameterConfiguration businessParameterConfiguration = new BusinessParameterConfiguration();
        if (this.existsByUK(bean)) {
            throw new AlreadyExistsException();
        }
        model = this.businessParameterRepository.save(this.toModel(model, bean));
        if (bean.getCurrentValue() != null && !bean.getCurrentValue().isEmpty()) {
            businessParameterConfiguration.setTo(bean.getDateApplyFrom());
            businessParameterConfiguration.setFrom(bean.getDateFrom());
            businessParameterConfiguration.setValue(bean.getCurrentValue());
            businessParameterConfiguration.setBusinessParameter(model);
            this.businessParameterConfigurationRepository.save(businessParameterConfiguration);
        }
        if (bean.getProgrammedValue() != null && !bean.getProgrammedValue().isEmpty()) {
            BusinessParameterConfiguration programmedValue = new BusinessParameterConfiguration();
            programmedValue.setFrom(bean.getDateApplyFrom());
            programmedValue.setValue(bean.getProgrammedValue());
            programmedValue.setBusinessParameter(model);
            this.businessParameterConfigurationRepository.save(programmedValue);
        }
        return this.toBean(model);
    }

    public BusinessParameterConfigurationBean getBusinessConfiguration(BigInteger id) {
        List<BusinessParameterConfiguration> businessConfigurationList = businessParameterConfigurationRepository.findBusinessParameterConfigurationListByBusinessParameterWithLowestDateFrom(id, OffsetDateTime.now());
        BusinessParameterConfigurationBean bean = new BusinessParameterConfigurationBean();
        if (!businessConfigurationList.isEmpty()) {
            BusinessParameterConfiguration businessParameterConfigurationFirst = businessConfigurationList.get(0);
            if (businessConfigurationList.size() == 1) {
                bean.setValue(businessParameterConfigurationFirst.getValue());
                bean.setFrom(businessParameterConfigurationFirst.getFrom());
                bean.setCurrentConfigurationId(businessParameterConfigurationFirst.getId());
            } else {
                for (BusinessParameterConfiguration bpc : businessConfigurationList) {
                    if (businessParameterConfigurationFirst.getFrom().equals(bpc.getTo())) {
                        bean.setValue(bpc.getValue());
                        bean.setFrom(bpc.getFrom());
                        bean.setTo(bpc.getTo());
                        bean.setProgrammedValue(businessParameterConfigurationFirst.getValue());
                        bean.setCurrentConfigurationId(bpc.getId());
                        bean.setProgrammedConfigurationId(bpc.getId());
                        bean.setCurrentConfigurationId(businessParameterConfigurationFirst.getId());
                    }
                }
            }
        }
        return bean;
    }

    public boolean validateKey(BusinessParameterBean bean) {
        Optional<BusinessParameter> businessParameterOptional = this.businessParameterRepository.findByKey(bean.getKey());
        if (businessParameterOptional.isPresent()) {
            BusinessParameterBean businessParameterBeanOptional = toBean(businessParameterOptional.get());
            if (!businessParameterBeanOptional.getId().equals(bean.getId())) {
                return true;
            }
        }
        return false;
    }

    public List<BusinessParameterConfigurationBean> getConfigurationParametersList(BigInteger busnessKey) {
        List<BusinessParameterConfiguration> businessParametersConfigurationModelList = this.businessParameterConfigurationRepository.findBusinessParameterConfigurationListByBusinessParameter(busnessKey);
        List<BusinessParameterConfigurationBean> beanList = new ArrayList<>();
        if (!businessParametersConfigurationModelList.isEmpty()) {
            for (BusinessParameterConfiguration model : businessParametersConfigurationModelList) {
                BusinessParameterConfigurationBean businessParametersConfigurationBean = businessParameterConfigurationService.toBean(model);
                beanList.add(businessParametersConfigurationBean);
            }
            return beanList;
        }
        return beanList;
    }

    public String getBusinessParameterForKey(String parameter) {
        Optional<BusinessParameter> businessParameter = businessParameterRepository.findByKey(parameter);
        if(!businessParameter.isPresent()) {
            return null;
        }

        String parameterResult = null;
        if (businessParameter.get().getRequired()) {
            parameterResult = businessParameter.get().getDefaultValue();
        }

        List<BusinessParameterConfiguration> parameterConfig = businessParameterConfigurationRepository
                .findBusinessParameterConfigurationListByBusinessParameter(businessParameter.get().getId());
        if (parameterConfig.isEmpty()) {
            return parameterResult;
        }

        OffsetDateTime nowTime = OffsetDateTime.now();
        for (BusinessParameterConfiguration config: parameterConfig) {
            if (config.getTo() == null) {
                if (nowTime.isAfter(config.getFrom())) {
                    parameterResult = config.getValue();
                    break;
                }
            } else {
                if (config.getFrom().equals(config.getTo())) {
                    if (config.getFrom().isBefore(nowTime)) {
                        parameterResult = config.getValue();
                        break;
                    }
                }
                if (config.getFrom().isBefore(config.getTo()) && nowTime.isAfter(config.getFrom()) && config.getTo().isAfter(nowTime)) {
                    parameterResult = config.getValue();
                    break;
                }
            }
        }

        return parameterResult;
    }
}
