package com.yn.bftl.thirdparty.common.service;

import com.google.common.base.Strings;
import com.yn.bftl.common.common.entity.Sequence;
import com.yn.bftl.common.common.entity.SequenceLettersTypeSelect;
import com.yn.bftl.common.common.entity.SequenceTypeSelect;
import com.yn.bftl.common.common.entity.SequenceVersion;
import com.yn.bftl.thirdparty.common.repository.SequenceRepository;
import com.yn.bftl.thirdparty.common.repository.SequenceVersionRepository;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.lang.invoke.MethodHandles;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoField;
import java.time.temporal.IsoFields;

@Component
public class SequenceService {

  private static final String DRAFT_PREFIX = "#";

  private static final String PATTERN_FULL_YEAR = "%YYYY",
      PATTERN_YEAR = "%YY",
      PATTERN_MONTH = "%M",
      PATTERN_FULL_MONTH = "%FM",
      PATTERN_DAY = "%D",
      PATTERN_FULL_DAY = "%FD",
      PATTERN_WEEK = "%WY",
      PADDING_STRING = "0";

  private final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  @Resource
  private SequenceVersionRepository sequenceVersionRepository;
  @Resource
  private AppBaseService appBaseService;
  @Resource
  private SequenceRepository sequenceRepo;

  public static boolean isYearValid(Sequence sequence) {

    boolean yearlyResetOk = sequence.getYearlyResetOk();

    if (!yearlyResetOk) {
      return true;
    }

    String seqPrefixe = StringUtils.defaultString(sequence.getPrefixe(), ""),
        seqSuffixe = StringUtils.defaultString(sequence.getSuffixe(), ""),
        seq = seqPrefixe + seqSuffixe;

    if (yearlyResetOk && !seq.contains(PATTERN_YEAR) && !seq.contains(PATTERN_FULL_YEAR)) {
      return false;
    }

    return true;
  }

  public static boolean isMonthValid(Sequence sequence) {

    boolean monthlyResetOk = sequence.getMonthlyResetOk();

    if (!monthlyResetOk) {
      return true;
    }

    String seqPrefixe = StringUtils.defaultString(sequence.getPrefixe(), ""),
        seqSuffixe = StringUtils.defaultString(sequence.getSuffixe(), ""),
        seq = seqPrefixe + seqSuffixe;

    if (monthlyResetOk
        && ((!seq.contains(PATTERN_MONTH) && !seq.contains(PATTERN_FULL_MONTH))
            || (!seq.contains(PATTERN_YEAR) && !seq.contains(PATTERN_FULL_YEAR)))) {
      return false;
    }

    return true;
  }

  public static boolean isSequenceLengthValid(Sequence sequence) {
    String seqPrefixe = StringUtils.defaultString(sequence.getPrefixe(), "").replaceAll("%", "");
    String seqSuffixe = StringUtils.defaultString(sequence.getSuffixe(), "").replaceAll("%", "");

    return (seqPrefixe.length() + seqSuffixe.length() + sequence.getPadding()) <= 14;
  }

  /**
   * Retourne une sequence en fonction du code, de la sté
   *
   * @return
   */
  public Sequence getSequence(String code) {

    if (code == null) {
      return null;
    }
    return sequenceRepo.findByCode(code);
  }

  /**
   * Retourne une sequence en fonction du code, de la sté
   *
   * @return
   */
  public String getSequenceNumber(String code) {

    Sequence sequence = getSequence(code);

    if (sequence == null) {
      return null;
    }

    return this.getSequenceNumber(sequence, appBaseService.getTodayDate());
  }

  /**
   * Retourne une sequence en fonction du code, de la sté
   *
   * @return
   */
  public boolean hasSequence(String code) {

    return getSequence(code) != null;
  }

  public String getSequenceNumber(Sequence sequence) {
    synchronized (this) {
      return getSequenceNumber(sequence, appBaseService.getTodayDate());
    }
  }

  /**
   * Fonction retournant une numéro de séquence depuis une séquence générique, et une date
   *
   * @param sequence
   * @return
   */
  @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
  public String getSequenceNumber(Sequence sequence, LocalDate refDate) {
      SequenceVersion sequenceVersion = getVersion(sequence, refDate);

      String seqPrefixe = StringUtils.defaultString(sequence.getPrefixe(), ""),
              seqSuffixe = StringUtils.defaultString(sequence.getSuffixe(), ""),
              sequenceValue;

      if (sequence.getSequenceTypeSelect() == SequenceTypeSelect.NUMBERS) {
        sequenceValue =
                StringUtils.leftPad(
                        sequenceVersion.getNextNum().toString(), sequence.getPadding(), PADDING_STRING);
      } else {
        sequenceValue = findNextLetterSequence(sequenceVersion);
      }
      sequenceVersion.setNextNum(sequenceVersion.getNextNum() + sequence.getToBeAdded());
      String nextSeq =
              (seqPrefixe + sequenceValue + seqSuffixe)
                      .replaceAll(PATTERN_FULL_YEAR, Integer.toString(refDate.get(ChronoField.YEAR_OF_ERA)))
                      .replaceAll(PATTERN_YEAR, refDate.format(DateTimeFormatter.ofPattern("yy")))
                      .replaceAll(PATTERN_MONTH, Integer.toString(refDate.getMonthValue()))
                      .replaceAll(PATTERN_FULL_MONTH, refDate.format(DateTimeFormatter.ofPattern("MM")))
                      .replaceAll(PATTERN_DAY, Integer.toString(refDate.getDayOfMonth()))
                      .replaceAll(PATTERN_FULL_DAY, refDate.format(DateTimeFormatter.ofPattern("dd")))
                      .replaceAll(
                              PATTERN_WEEK, Integer.toString(refDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR)));

      log.debug("nextSeq : : : : {}", nextSeq);

      sequenceVersionRepository.save(sequenceVersion);

      return nextSeq;
  }

  private String findNextLetterSequence(SequenceVersion sequenceVersion) {
    long n = sequenceVersion.getNextNum();
    char[] buf = new char[(int) Math.floor(Math.log(25 * (n + 1)) / Math.log(26))];
    for (int i = buf.length - 1; i >= 0; i--) {
      n--;
      buf[i] = (char) ('A' + n % 26);
      n /= 26;
    }
    if (sequenceVersion.getSequence().getSequenceLettersTypeSelect()
        == SequenceLettersTypeSelect.UPPERCASE) {
      return new String(buf);
    }
    return new String(buf).toLowerCase();
  }

  protected SequenceVersion getVersion(Sequence sequence, LocalDate refDate) {

    log.debug("Reference date : : : : {}", refDate);

    if (sequence.getMonthlyResetOk()) {
      return getVersionByMonth(sequence, refDate);
    }
    if (sequence.getYearlyResetOk()) {
      return getVersionByYear(sequence, refDate);
    }
    return getVersionByDate(sequence, refDate);
  }

  protected SequenceVersion getVersionByDate(Sequence sequence, LocalDate refDate) {

    SequenceVersion sequenceVersion = sequenceVersionRepository.findByDate(sequence, refDate);
    if (sequenceVersion == null) {
      sequenceVersion = new SequenceVersion(sequence, refDate, null, 1L);
      sequenceVersion.setVersion(0);
    }

    return sequenceVersion;
  }

  protected SequenceVersion getVersionByMonth(Sequence sequence, LocalDate refDate) {

    SequenceVersion sequenceVersion =
        sequenceVersionRepository.findByMonth(sequence, refDate.getMonthValue(), refDate.getYear());
    if (sequenceVersion == null) {
      sequenceVersion =
          new SequenceVersion(
              sequence,
              refDate.withDayOfMonth(1),
              refDate.withDayOfMonth(refDate.lengthOfMonth()),
              1L);
      sequenceVersion.setVersion(0);
    }

    return sequenceVersion;
  }

  protected SequenceVersion getVersionByYear(Sequence sequence, LocalDate refDate) {

    SequenceVersion sequenceVersion =
        sequenceVersionRepository.findByYear(sequence, refDate.getYear());
    if (sequenceVersion == null) {
      sequenceVersion =
          new SequenceVersion(
              sequence,
              refDate.withDayOfMonth(1),
              refDate.withDayOfMonth(refDate.lengthOfMonth()),
              1L);
      sequenceVersion.setVersion(0);
    }

    return sequenceVersion;
  }

  /**
   * Check whether a sequence number is empty or draft.
   *
   * <p>Also consider '*' as draft character for backward compatibility.
   *
   * @param sequenceNumber
   * @return
   */
  public boolean isEmptyOrDraftSequenceNumber(String sequenceNumber) {
    return Strings.isNullOrEmpty(sequenceNumber)
        || sequenceNumber.matches(String.format("[\\%s\\*]\\d+", DRAFT_PREFIX));
  }

  /**
   * Computes sequence full name
   *
   * @param sequence Sequence to compute full name
   */
  public String computeFullName(Sequence sequence) {
    StringBuilder fn = new StringBuilder();

    if (sequence.getPrefixe() != null) {
      fn.append(sequence.getPrefixe());
    }

    for (int i = 0; i < sequence.getPadding(); i++) {
      fn.append("X");
    }

    if (sequence.getSuffixe() != null) {
      fn.append(sequence.getSuffixe());
    }

    fn.append(" - ");
    fn.append(sequence.getName());

    return fn.toString();
  }
}
