package com.ffii.core.settings.service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ffii.core.exception.InternalServerErrorException;
import com.ffii.core.support.AbstractIdEntityService;
import com.ffii.core.support.JdbcDao;
import com.ffii.core.settings.dao.SettingsDao;
import com.ffii.core.settings.entity.Settings;

@Service
public class SettingsService extends AbstractIdEntityService<Settings, Integer, SettingsDao> {

  public SettingsService(JdbcDao jdbcDao, SettingsDao dao) {
    super(jdbcDao, dao);
  }

  public Optional<Settings> findByName(String name) {
    return this.dao.findByName(name);
  }

  public boolean validateType(String type, String value) {
    if (StringUtils.isBlank(type))
      return true;

    if (Settings.TYPE_STRING.equals(type))
      return true;

    if (Settings.TYPE_BOOLEAN.equals(type)) {
      return Settings.VALUE_BOOLEAN_TRUE.equals(value) || Settings.VALUE_BOOLEAN_FALSE.equals(value);
    }

    if (Settings.TYPE_INT.equals(type)) {
      try {
        Integer.parseInt(value);
        return true;
      } catch (NumberFormatException e) {
        return false;
      }
    }
    if (Settings.TYPE_FLOAT.equals(type)) {
      try {
        Float.parseFloat(value);
        return true;
      } catch (NumberFormatException e) {
        return false;
      }
    }
    if (Settings.TYPE_DATE.equals(type)) {
      try {
        LocalDate.parse(value, DateTimeFormatter.ISO_DATE);
        return true;
      } catch (DateTimeParseException e) {
        return false;
      }
    }
    if (Settings.TYPE_TIME.equals(type)) {
      try {
        LocalTime.parse(value, DateTimeFormatter.ISO_TIME);
        return true;
      } catch (DateTimeParseException e) {
        return false;
      }
    }
    if (Settings.TYPE_DATETIME.equals(type)) {
      try {
        LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME);
        return true;
      } catch (DateTimeParseException e) {
        return false;
      }
    }

    if (StringUtils.indexOf(type, "/") >= 0) {
      for (String t : type.split("/")) {
        if (t.equals(value))
          return true;
      }
      return false;
    }

    return false;
  }

  @Transactional(rollbackFor = Exception.class)
  public void update(String name, String value) {
    Settings settings = this.findByName(name)
        .orElseThrow(InternalServerErrorException::new);
    if (!validateType(settings.getType(), value)) {
      throw new InternalServerErrorException();
    }
    settings.setValue(value);
    this.save(settings);
  }

  @Transactional(rollbackFor = Exception.class)
  public void update(String name, LocalDate date) {
    this.update(name, date.format(DateTimeFormatter.ISO_DATE));
  }

  @Transactional(rollbackFor = Exception.class)
  public void update(String name, LocalDateTime datetime) {
    this.update(name, datetime.format(DateTimeFormatter.ISO_DATE_TIME));
  }

  @Transactional(rollbackFor = Exception.class)
  public void update(String name, LocalTime time) {
    this.update(name, time.format(DateTimeFormatter.ISO_TIME));
  }

  public String getString(String name) {
    return this.findByName(name)
        .map(Settings::getValue)
        .orElseThrow(InternalServerErrorException::new);
  }

  public int getInt(String name) {
    return this.findByName(name)
        .map(Settings::getValue)
        .map(v -> {
          try {
            return Integer.parseInt(v);
          } catch (final NumberFormatException nfe) {
            return null;
          }
        })
        .orElseThrow(InternalServerErrorException::new);
  }

  public double getDouble(String name) {
    return this.findByName(name)
        .map(Settings::getValue)
        .map(v -> {
          try {
            return Double.parseDouble(v);
          } catch (final NumberFormatException nfe) {
            return null;
          }
        })
        .orElseThrow(InternalServerErrorException::new);
  }

  public boolean getBoolean(String name) {
    return this.findByName(name)
        .map(Settings::getValue)
        .map(Settings.VALUE_BOOLEAN_TRUE::equals)
        .orElseThrow(InternalServerErrorException::new);
  }

  public LocalDate getDate(String name) {
    return this.getDate(name, DateTimeFormatter.ISO_DATE);
  }

  private LocalDate getDate(String name, DateTimeFormatter formatter) {
    return this.findByName(name)
        .map(Settings::getValue)
        .map(v -> {
          try {
            return LocalDate.parse(v, formatter);
          } catch (DateTimeParseException e) {
            return null;
          }
        })
        .orElseThrow(InternalServerErrorException::new);
  }

  public LocalDateTime getDatetime(String name) {
    return this.getDatetime(name, DateTimeFormatter.ISO_DATE_TIME);
  }

  private LocalDateTime getDatetime(String name, DateTimeFormatter formatter) {
    return this.findByName(name)
        .map(Settings::getValue)
        .map(v -> {
          try {
            return LocalDateTime.parse(v, formatter);
          } catch (DateTimeParseException e) {
            return null;
          }
        })
        .orElseThrow(InternalServerErrorException::new);
  }

  public LocalTime getTime(String name) {
    return this.getTime(name, DateTimeFormatter.ISO_TIME);
  }

  private LocalTime getTime(String name, DateTimeFormatter formatter) {
    return this.findByName(name)
        .map(Settings::getValue)
        .map(v -> {
          try {
            return LocalTime.parse(v, formatter);
          } catch (DateTimeParseException e) {
            return null;
          }
        })
        .orElseThrow(InternalServerErrorException::new);
  }

}
